如何将此Parallel.ForEach代码转换为async / await

时间:2015-10-02 01:42:37

标签: c# asynchronous

我遇到一些麻烦让我的头脑异步/等待。我正在帮助使用具有以下代码的现有代码库(简化为了简洁起见):

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收到警告,我的代码将同步执行,或者我不能在异步方法之外使用等待关键字,所以我确定我只是遗漏了一些东西简单。

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

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

2 个答案:

答案 0 :(得分:3)

让事情变得异步的关键&#34;是从树叶开始。在这种情况下,从您的网络代码(未显示)开始,并将您拥有的任何同步呼叫(例如,WebClient.DownloadString)更改为相应的异步呼叫(例如,HttpClient.GetStringAsync)。然后调用await

使用await会强制调用方法为async,并将其返回类型从T更改为Task<T>。此时添加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)
{
  ...
}

答案 1 :(得分: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
}