Do not overdo parallelism and asynchronous
Sometimes it’s easy to little bit overdo the need for having everything asynchronous and parallel. Quite often in last few weeks I’ve seen methods similar to this one.
Console.WriteLine("Starting");
Parallel.For(1, 10, async i =>
{
await Task.Delay(200);
Console.WriteLine(i);
});
Console.WriteLine("Finished");
What’s the result? For somebody maybe surprisingly:
Starting
Finished
Why? What we see here is a two pieces “process”. First the Parallel.For
. This methods runs the provided method in parallel (for our discussion it doesn’t matter how and what exactly that means) and waits for all methods to complete. The lambda expression we’re providing is asynchronous. And though the async
/await
simplified the programming a lot, it’s still standing on basic principles around Tasks
. And that’s the key for understanding what’s wrong. The async lambda is basically (I’m simplifying here) starting a task to do the work and returning that task so you can eventually (a)wait it to complete. But the Parallel.For
care about the method (all of them) returning, not (a)waiting tasks (it’s actually an Action<int>
also known as “async void” hence it has no idea about the task inside). And here you have it.
The question that’s left is, how to fix it? 😃 Probably easiest way is to extract the lambda to method and wait for that task to complete. That will make the method blocking so the Parallel.For
is not going to end prematurely.
static void Main(string[] args)
{
Console.WriteLine("Starting");
Parallel.For(1, 10, i => Action(i).Wait());
Console.WriteLine("Finished");
}
static async Task Action(int i)
{
await Task.Delay(200);
Console.WriteLine(i);
}
But wait. That’s a little bit crazy, isn’t it? We have tasks running asynchronously and we’re spinning Parallel.For
and waiting??? You can actually run the loop starting the asynchronous methods capturing the returned tasks and then use Task.WaitAll
or if you want to go really deep async 😉 you can use Task.WhenAll
and await
it.
If you’d like to see something like ForEachAsync
(or maybe ForAsync
) you can get and inspiration and other interesting notes from this Stephen Toub’s blog post.