Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert this Parallel.ForEach code to async/await

I'm having some trouble getting my head around async/await. I'm helping with an existing code base that has the following code (simplified, for brevity):

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
}

How can i convert this code to asynchronous code instead of parallel using async/await? I'm suffering from some pretty severe performance issues that I believe are a result of using a parallel approach to multiple network I/O operations.

I've tried several approaches myself but I'm getting warnings from Visual Studio that my code will execute synchronously or that I can't use await keywords outside of an async method so I'm sure I'm just missing something simple.

EDIT #1: I'm open to alternatives to async/await as well. That just seems to be the proper approach based on my reading so far.

EDIT #2: This application is a Windows Service. It calls out to several "buyers" to ask them to bid on a particular piece of data. I need ALL of the bids back before processing can continue.

like image 332
Scott Avatar asked Oct 02 '15 01:10

Scott


1 Answers

The key to "making things async" is to start at the leaves. In this case, start in your network code (not shown), and change whatever synchronous call you have (e.g., WebClient.DownloadString) to the corresponding asynchronous call (e.g., HttpClient.GetStringAsync). Then await that call.

Using await will force the calling method to be async, and change its return type from T to Task<T>. It is also a good idea at this point to add the Async suffix so you're following the well-known convention. Then take all of that method's callers and change them to use await as well, which will then require them to be async, etc. Repeat until you have a BidAsync method to use.

Then you should look at replacing your parallel loop; this is pretty easy to do with Task.WhenAll:

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

foreach (var result in results)
{
  ...
}
like image 108
Stephen Cleary Avatar answered Oct 09 '22 18:10

Stephen Cleary