Let's compare the behaviour of your line:
await Task.WhenAll(tasks.AsParallel().Select(async task => await task));
in contrast with:
await Task.WhenAll(tasks);
What are you delegating to PLINQ in the first case? Only the await
operation, which does basically nothing - it invokes the async
/await
machinery to wait for one task
. So you're setting up a PLINQ query that does all the heavy work of partitioning and merging the results of an operation that amounts to "do nothing until this task
completes". I doubt that is what you want.
If you have another await in your methods, Task.WhenAll() will not help as Async methods are not parallel.
I couldn't find that in any of the answers to the linked questions, except for one comment under the question itself. I'd say that it's probably a misconception, stemming from the fact that async
/await
doesn't magically turn your code into concurrent code. But, assuming you're in an environment without a custom SynchronizationContext
(so not an ASP or WPF app), continuations to async
functions will be scheduled on the thread pool and possibly run in parallel. I'll delegate you to this answer to shed some light on that. That basically means that if your SendAsync
looks something like this:
Task SendAsync(IMessage message)
{
// Synchronous initialization code.
await something;
// Continuation code.
}
Then:
- The first part before await runs synchronously. If this part is heavyweight, you should introduce parallelism in
SendAll
so that the initialization code is run in parallel.
await
works as usual, waiting for work to complete without using up any threads.
- The continuation code will be scheduled on the thread pool, so if a few
await
s finish up at the same time their continuations might be run in parallel if there's enough threads in the thread pool.
All of the above is assuming that await something
actually awaits asynchronously. If there's a chance that await something
completes synchronously, then the continuation code will also run synchronously.
Now there is a catch. In the question you linked one of the answers states:
Task.WhenAll()
has a tendency to become unperformant with large scale/amount of tasks firing simultaneously - without moderation/throttling.
Now I don't know if that's true, since I weren't able to find any other source claiming that. I guess it's possible and in that case it might actually be beneficial to invoke PLINQ to deal with partitioning and throttling for you. However, you said you typically handle 1-5 functions, so you shouldn't worry about this.
So to summarize, parallelism is hard and the correct approach depends on how exactly your SendAsync
method looks like. If it has heavyweight initialization code and that's what you want to parallelise, you should run all the calls to SendAsync
in parallel. Otherwise, async
/await
will be implicitly using the thread pool anyway, so your call to PLINQ is redundant.