Does Task.WhenAll wait for all the tasks in case of exceptions

Will the other one complete?

It won't be stopped as a result of the other one failing.

But will it complete?

Task.When will wait for all to complete, whether any or none fail. I just tested with this to verify - and it took 5 seconds to complete:

Task allTasks = Task.WhenAll(getClientToken, getVault, Task.Delay(5000)); 

If you want to group the tasks you can create a 'new task', then await that.

Task allTasks = Task.WhenAll(getClientToken, getVault, Task.Delay(5000)); 

try 
{
    await allTasks;

} catch (Exception ex) 
{

   // ex is the 'unwrapped' actual exception
   // I'm not actually sure if it's the first task to fail, or the first in the list that failed

   // Handle all if needed
   Exceptions[] allExceptions = allTasks.Exceptions;

   // OR
   // just get the result from the task / exception
   if (getVault.Status == TaskStatus.Faulted) 
   {
       ...
   }
}

Had the same question and tested by myself. In short:

  • It always wait for all tasks to finish.

  • The first exception is thrown if there is any after all tasks are finished (crash if you don't catch).

  • For all exceptions, keep the Task instance returned by Task.WhenAll and use Exception.InnerExceptions property.

Here's my test:

    static async Task Main(string[] args)
    {
        var tasks = new[] { Foo1(), Foo2(), Foo3() };

        Task t = null;
        try
        {
            t = Task.WhenAll(tasks);
            await t;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
        }

        Console.WriteLine("All have run.");

        if (t.Exception != null) 
        {
            foreach (var ex in t.Exception.InnerExceptions)
            {
                Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
            }
        }

    }

    static async Task Foo1()
    {
        await Task.Delay(50);
        throw new ArgumentException("zzz");
    }

    static async Task Foo2()
    {
        await Task.Delay(1000);
        Console.WriteLine("Foo 2");
        throw new FieldAccessException("xxx");
    }

    static async Task Foo3()
    {
        for (int i = 0; i < 10; i++)
        {
            await Task.Delay(200);
            Console.WriteLine("Foo 3");
        }
    }

Output:

Foo 3
Foo 3
Foo 3
Foo 3
Foo 2
Foo 3
Foo 3
Foo 3
Foo 3
Foo 3
Foo 3
ArgumentException: zzz
All have run.
ArgumentException: zzz
FieldAccessException: xxx

Just run this code to test it:

private static async Task TestTaskWhenAll()
{
    try
    {
        await Task.WhenAll(
            ShortOperationAsync(),
            LongOperationAsync()
        );
    }
    catch (Exception exception)
    {
        Console.WriteLine(exception.Message); // Short operation exception
        Debugger.Break();
    }
}

private static async Task ShortOperationAsync()
{
    await Task.Delay(1000);
    throw new InvalidTimeZoneException("Short operation exception");

}

private static async Task LongOperationAsync()
{
    await Task.Delay(5000);
    throw new ArgumentException("Long operation exception");
}

Debugger will stop in 5 seconds. Both exceptions are thrown, but Debugger.Break() is hit only once. What is more, the exception value is not AggregateException, but InvalidTimeZoneException. This is because of new async/await which does the unwrapping into the actual exception. You can read more here. If you want to read other Exceptions (not only the first one), you would have to read them from the Task returned from WhenAll method call.