I was reading this post about Parallel.ForEach
where it was stated that "Parallel.ForEach is not compatible with passing in a async method."
So, to check I write this code:
static async Task Main(string[] args)
{
var results = new ConcurrentDictionary<string, int>();
Parallel.ForEach(Enumerable.Range(0, 100), async index =>
{
var res = await DoAsyncJob(index);
results.TryAdd(index.ToString(), res);
});
Console.ReadLine();
}
static async Task<int> DoAsyncJob(int i)
{
Thread.Sleep(100);
return await Task.FromResult(i * 10);
}
This code fills in the results
dictionary concurrently.
By the way, I created a dictionary of type ConcurrentDictionary<string, int>
because in case I have ConcurrentDictionary<int, int>
when I explore its elements in debug mode I see that elements are sorted by the key and I thought that elenents was added consequently.
So, I want to know is my code is valid? If it "is not compatible with passing in a async method" why it works well?
This code works only because DoAsyncJob
isn't really an asynchronous method. async
doesn't make a method work asynchronously. Awaiting a completed task like that returned by Task.FromResult
is synchronous too. async Task Main
doesn't contain any asynchronous code, which results in a compiler warning.
An example that demonstrates how Parallel.ForEach
doesn't work with asynchronous methods should call a real asynchronous method:
static async Task Main(string[] args)
{
var results = new ConcurrentDictionary<string, int>();
Parallel.ForEach(Enumerable.Range(0, 100), async index =>
{
var res = await DoAsyncJob(index);
results.TryAdd(index.ToString(), res);
});
Console.WriteLine($"Items in dictionary {results.Count}");
}
static async Task<int> DoAsyncJob(int i)
{
await Task.Delay(100);
return i * 10;
}
The result will be
Items in dictionary 0
Parallel.ForEach has no overload accepting a Func<Task>
, it accepts only Action
delegates. This means it can't await any asynchronous operations.
async index
is accepted because it's implicitly an async void
delegate. As far as Parallel.ForEach
is concerned, it's just an Action<int>
.
The result is that Parallel.ForEach
fires off 100 tasks and never waits for them to complete. That's why the dictionary is still empty when the application terminates.