Reputation: 13921
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.
Upvotes: 5
Views: 2073
Reputation: 456342
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)
{
...
}
Upvotes: 4
Reputation: 4408
Basically, to make use of async-await
, the Bid
method should have this signature instead of the current one:
public async Task<Result> BidAsync(BuyerContext buyerContext);
This will allow you to use await
in this method. Now, every time you make a network call, you basically need to await
it. For example, here's how to modify the call and signature of a synchronous method to an asynchronous one.
Before
//Signature
public string ReceiveStringFromClient();
//Call
string messageFromClient = ReceiveStringFromClient();
After
//Signature
public Task<string> ReceiveStringFromClientAsync();
//Call
string messageFromClient = await ReceiveStringFromClientAsync();
If you still need to be able to make synchronous calls to these methods, I would recommend creating new ones suffixed with "Async".
Now you need to do this on every level until you reach your network calls, at which point you'll be able to await .Net's async
methods. They normally have the same name as their synchronous version, suffixed with "Async".
Once you've done all that, you can make use of this in your main code. I would do something along these lines:
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
}
Upvotes: 3