【问题标题】:How to convert this Parallel.ForEach code to async/await如何将此 Parallel.ForEach 代码转换为 async/await
【发布时间】:2015-10-02 01:42:37
【问题描述】:

我在处理异步/等待时遇到了一些麻烦。我正在帮助具有以下代码的现有代码库(为简洁起见进行了简化):

List<BuyerContext> buyerContexts = GetBuyers();
var results = new List<Result>();

Parallel.ForEach(buyerContexts, buyerContext =>
{
    //The following call creates a connection to a remote web server that 
    //can take up to 15 seconds to respond
    var result = Bid(buyerContext);

    if (result != null)
        results.Add(result);
}

foreach (var result in results)
{
  // do some work here that is predicated on the 
  // Parallel.ForEach having completed all of its calls
}

如何使用 async/await 将此代码转换为异步代码而不是并行代码?我遇到了一些非常严重的性能问题,我认为这是对多个网络 I/O 操作使用并行方法的结果。

我自己尝试了几种方法,但我从 Visual Studio 收到警告说我的代码将同步执行,或者我不能在异步方法之外使用 await 关键字,所以我确定我只是错过了一些东西很简单。

编辑#1:我也愿意接受 async/await 的替代方案。根据我目前的阅读,这似乎是正确的方法。

编辑#2:此应用程序是 Windows 服务。它呼吁几个“买家”要求他们对特定的数据进行投标。在继续处理之前,我需要返回所有出价。

【问题讨论】:

  • 可以添加Bid方法定义吗?如果它与网络相关,那么查看网络代码真的很有帮助:)
  • 听起来像是一个非常昂贵的循环调用。您需要预先获得所有结果吗?你能不能得到一些结果,然后只在用户要求更多时才返回?
  • 查看stackoverflow.com/questions/9290498/… 以获得简单的解决方案。
  • Bid 方法有大量的逻辑,建立网络连接的实际代码隐藏在一个抽象基类中几个层次。在这里发布所有内容是不谨慎的。
  • 在我看来,async 更有可能是这里的解决方案。 Parallel.ForEach 只是在并行调用,网络接口是造成这里变慢的罪魁祸首

标签: c# asynchronous


【解决方案1】:

“使事情异步”的关键是从叶子开始。在这种情况下,从您的网络代码(未显示)开始,并将您拥有的任何同步调用(例如,WebClient.DownloadString)更改为相应的异步调用(例如,HttpClient.GetStringAsync)。然后await 那个电话。

使用await将强制调用方法为async,并将其返回类型从T更改为Task&lt;T&gt;。此时添加Async 后缀也是一个好主意,这样您就可以关注the well-known convention。然后获取所有 那个 方法的调用者并将它们更改为也使用await,这将要求它们为async 等。重复直到你有一个BidAsync 方法可以使用。

那么你应该考虑替换你的并行循环;使用Task.WhenAll 很容易做到这一点:

List<BuyerContext> buyerContexts = GetBuyers();
var tasks = buyerContexts.Select(buyerContext => BidAsync(buyerContext));
var results = await Task.WhenAll(tasks);

foreach (var result in results)
{
  ...
}

【讨论】:

    【解决方案2】:

    基本上,要使用async-awaitBid 方法应该有这个签名,而不是当前的:

    public async Task<Result> BidAsync(BuyerContext buyerContext);
    

    这将允许您在此方法中使用await。现在,每次打网络电话,基本上都需要await它。例如,这里是如何将同步方法的调用和签名修改为异步方法。

    之前

    //Signature
    public string ReceiveStringFromClient();
    
    //Call
    string messageFromClient = ReceiveStringFromClient();
    

    之后

    //Signature
    public Task<string> ReceiveStringFromClientAsync();
    
    //Call
    string messageFromClient = await ReceiveStringFromClientAsync();
    

    如果您仍然需要能够对这些方法进行同步调用,我建议创建以“Async”为后缀的新方法。

    现在您需要在每个级别上执行此操作,直到您到达网络调用,此时您将能够等待 .Net 的 async 方法。它们通常与同步版本同名,后缀为“Async”。

    完成所有这些后,您就可以在主代码中使用它。我会按照这些思路做一些事情:

    List<BuyerContext> buyerContexts = GetBuyers();
    var results = new List<Result>();
    
    List<Task> tasks = new List<Task>();
    
    //There really is no need for Parallel.ForEach unless you have hundreds of thousands of requests to make.
    //If that's the case, I hope you have a good network interface!
    foreach (var buyerContext in buyerContexts)
    {
        var task = Task.Run(async () =>
        {
            var result = await BidAsync(buyerContext);        
    
            if (result != null)
                results.Add(result);
        });
    
        tasks.Add(task);
    }
    
    //Block the current thread until all the calls are completed
    Task.WaitAll(tasks);
    
    foreach (var result in results)
    {
      // do some work here that is predicated on the 
      // Parallel.ForEach having completed all of its calls
    }
    

    【讨论】:

    • 是的,我从单行改为变量+添加但忘记了第二行,修复!
    • 谢谢!一个问题:异步方法必须以 Async() 结尾吗?询问的原因是 Bid() 方法包含对通过接口实现的方法的调用。如果我必须将方法名称更改为 Async() 我必须更新大约 50 个项目。 :)
    • 请注意,除非 bidasync 是真正异步的,否则它将具有与原始版本完全相同的性能。获得更高性能的最简单方法是增加线程池大小。
    猜你喜欢
    • 1970-01-01
    • 2012-08-28
    • 2021-12-05
    • 2023-03-11
    • 2022-03-10
    • 1970-01-01
    • 2018-12-12
    相关资源
    最近更新 更多