Why should I prefer single 'await Task.WhenAll' over multiple awaits?

Yes, use WhenAll because it propagates all errors at once. With the multiple awaits, you lose errors if one of the earlier awaits throws.

Another important difference is that WhenAll will wait for all tasks to complete even in the presence of failures (faulted or canceled tasks). Awaiting manually in sequence would cause unexpected concurrency because the part of your program that wants to wait will actually continue early.

I think it also makes reading the code easier because the semantics that you want are directly documented in code.


My understanding is that the main reason to prefer Task.WhenAll to multiple awaits is performance / task "churning": the DoWork1 method does something like this:

  • start with a given context
  • save the context
  • wait for t1
  • restore the original context
  • save the context
  • wait for t2
  • restore the original context
  • save the context
  • wait for t3
  • restore the original context

By contrast, DoWork2 does this:

  • start with a given context
  • save the context
  • wait for all of t1, t2 and t3
  • restore the original context

Whether this is a big enough deal for your particular case is, of course, "context-dependent" (pardon the pun).


An asynchronous method is implemented as a state-machine. It is possible to write methods so that they are not compiled into state-machines, this is often referred to as a fast-track async method. These can be implemented like so:

public Task DoSomethingAsync()
{
    return DoSomethingElseAsync();
}

When using Task.WhenAll it is possible to maintain this fast-track code while still ensuring the caller is able to wait for all tasks to be completed, e.g.:

public Task DoSomethingAsync()
{
    var t1 = DoTaskAsync("t2.1", 3000);
    var t2 = DoTaskAsync("t2.2", 2000);
    var t3 = DoTaskAsync("t2.3", 1000);

    return Task.WhenAll(t1, t2, t3);
}