1

Are those lines behaving exactly the same (Including Exceptionhandling via AggregateException)?

Task.WhenAll(taskList).Wait()
Task.WaitAll(taskList)

Thanks

To Wa
  • 43
  • 7
  • 1
    Does this answer your question? [WaitAll vs WhenAll](https://stackoverflow.com/questions/6123406/waitall-vs-whenall) – Mark C. Sep 22 '21 at 12:43
  • 1
    No. Both block but the first is worse because it didn't have to. It' starts awaiting asynchronously only to get blocked by `Wait()`. Don't block at all instead, use `await Task.WhenAll()` – Panagiotis Kanavos Sep 22 '21 at 12:43
  • 2
    If anything, `Task.WhenAll(taskList).Wait()` may result in deeper nesting of the original exceptions, and an `AggregateException(AggregateException(Exception[]))` instead of an `AggregateException(Exception[]))`. Use `await Task.WhenAll()` instead to get the first actual `Exception` – Panagiotis Kanavos Sep 22 '21 at 12:48
  • What is the type of the `taskList`? Is it `List` or `List>`? – Theodor Zoulias Sep 22 '21 at 16:12
  • @MarkC. : No, not really, this post brought me to this question. @PanagiotisKanavos : Yep, I know that, but in my special case, I have to use Wait() and cannot use await. @TheodorZoulias : In my particular case a `List>`, but do you think that matters for my question? Thanks @all for your answers. I couldn't identify any difference between these lines, but i will investigate the nesting of the exceptions – To Wa Sep 23 '21 at 05:52

1 Answers1

1

Let's find out by experimentation:

var task1 = Task.FromResult(13);
var task2 = Task.FromCanceled<int>(new CancellationToken(true));
var task3 = Task.FromCanceled<int>(new CancellationToken(true));
var task4 = Task.FromException<int>(new ApplicationException());
var task5 = Task.FromException<int>(new OverflowException());
Test("Successful+Canceled+Canceled", new[] { task1, task2, task3 });
Test("Successful+Failed+Failed", new[] { task1, task4, task5 });
Test("Successful+Canceled+Failed+Failed", new[] { task1, task2, task4, task5 });
Test("Successful+Canceled+Canceled+Failed", new[] { task1, task2, task3, task4 });

static void Test(string title, Task<int>[] tasks)
{
    Console.WriteLine();
    Console.WriteLine(title);
    try { Task.WaitAll(tasks); }
    catch (AggregateException ex)
    {
        Console.WriteLine($"WaitAll():      {ToString(ex)}");
    }
    try { Task.WhenAll(tasks).Wait(); }
    catch (AggregateException ex)
    {
        Console.WriteLine($"WhenAll.Wait(): {ToString(ex)}");
    }
}

static string ToString(AggregateException aex) {
    return $"({aex.InnerExceptions.Count}) " +
        String.Join(", ", aex.InnerExceptions.Select(ex => ex.GetType().Name));
}

Output:

Successful+Canceled+Canceled
WaitAll():      (2) TaskCanceledException, TaskCanceledException
WhenAll.Wait(): (1) TaskCanceledException

Successful+Failed+Failed
WaitAll():      (2) ApplicationException, OverflowException
WhenAll.Wait(): (2) ApplicationException, OverflowException

Successful+Canceled+Failed+Failed
WaitAll():      (3) TaskCanceledException, ApplicationException, OverflowException
WhenAll.Wait(): (2) ApplicationException, OverflowException

Successful+Canceled+Canceled+Failed
WaitAll():      (3) TaskCanceledException, TaskCanceledException, ApplicationException
WhenAll.Wait(): (1) ApplicationException

Try it on Fiddle.

What we see is that the Task.WaitAll() method propagates the exceptions of the tasks as they are, while the Task.WhenAll().Wait() approach propagates only a single TaskCanceledException, and only when no other type of exception has occurred.

It should also be mentioned that with the Task.WaitAll you get more options out of the box, like millisecondsTimeout, or cancellationToken, or both.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104