500

What is the difference between Task.WaitAll() and Task.WhenAll() from the Async CTP? Can you provide some sample code to illustrate the different use cases?

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
Yaron Levi
  • 12,535
  • 16
  • 69
  • 118

4 Answers4

728

Task.WaitAll blocks the current thread until everything has completed.

Task.WhenAll returns a task which represents the action of waiting until everything has completed.

That means that from an async method, you can use:

await Task.WhenAll(tasks);

... which means your method will continue when everything's completed, but you won't tie up a thread to just hang around until that time.

Alberto Solano
  • 7,972
  • 3
  • 38
  • 61
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Does this assume all the tasks you're waiting on are actually doing work on another thread? – Razor May 17 '14 at 03:43
  • 1
    @VincePanuccio As far as i know, a new thread will be used for each task if *needed*, the framework will get the context of the code and if its long-running operation then probably a new thread will be used. – ilansch Jan 15 '15 at 07:55
  • 4
    After much reading, It is clear that async has nothing to do with threads http://blog.stephencleary.com/2013/11/there-is-no-thread.html – Razor Jan 18 '15 at 11:45
  • 26
    @Vince: I think "nothing to do with threads" is an overstatement, and it's important to understand how async operations interact with threads. – Jon Skeet Jan 18 '15 at 11:47
  • 1
    @JonSkeet: I think statement `await TaskEx.WhenAll(tasks)` also block the current thread as well. – KevinBui Sep 18 '15 at 10:20
  • 11
    @KevinBui: No, it shouldn't *block* it - it will *await* the task returned by `WhenAll`, but that's not the same as blocking the thread. – Jon Skeet Sep 18 '15 at 10:24
  • 4
    @JonSkeet Perhaps the precise distinction between those two is too subtle for me. Can you point me (and possibly, the rest of us) at some reference that will make clear the difference? – CatShoes Dec 01 '15 at 21:26
  • 200
    @CatShoes: Not really - I've explained it as well as I can already. I guess I could give an analogy - it's like the difference between ordering a takeaway and then standing by the door waiting for it to arrive, vs ordering a takeaway, doing other stuff and then opening the door when the courier arrives... – Jon Skeet Dec 01 '15 at 21:37
  • OK. The await keyword (when used properly all the way down) uses a software interrupt to reenter code at the spot where it is placed when the software interrupt triggers. Then it immediately exits because the software interrupt will bring it back. So, there is no thread, even the current one has exited. – PRMan Jan 06 '22 at 16:44
  • 1
    @PRMan: Well the await keyword causes `OnCompleted` to be called provide the callback (assuming the awaitable hasn't already completed). Exactly how that's implemented is precisely that - an implementation detail. – Jon Skeet Jan 06 '22 at 16:52
126

While JonSkeet's answer explains the difference in a typically excellent way there is another difference: exception handling.

Task.WaitAll throws an AggregateException when any of the tasks throws and you can examine all thrown exceptions. The await in await Task.WhenAll unwraps the AggregateException and 'returns' only the first exception.

When the program below executes with await Task.WhenAll(taskArray) the output is as follows.

19/11/2016 12:18:37 AM: Task 1 started
19/11/2016 12:18:37 AM: Task 3 started
19/11/2016 12:18:37 AM: Task 2 started
Caught Exception in Main at 19/11/2016 12:18:40 AM: Task 1 throwing at 19/11/2016 12:18:38 AM
Done.

When the program below is executed with Task.WaitAll(taskArray) the output is as follows.

19/11/2016 12:19:29 AM: Task 1 started
19/11/2016 12:19:29 AM: Task 2 started
19/11/2016 12:19:29 AM: Task 3 started
Caught AggregateException in Main at 19/11/2016 12:19:32 AM: Task 1 throwing at 19/11/2016 12:19:30 AM
Caught AggregateException in Main at 19/11/2016 12:19:32 AM: Task 2 throwing at 19/11/2016 12:19:31 AM
Caught AggregateException in Main at 19/11/2016 12:19:32 AM: Task 3 throwing at 19/11/2016 12:19:32 AM
Done.

The program:

class MyAmazingProgram
{
    public class CustomException : Exception
    {
        public CustomException(String message) : base(message)
        { }
    }

    static void WaitAndThrow(int id, int waitInMs)
    {
        Console.WriteLine($"{DateTime.UtcNow}: Task {id} started");

        Thread.Sleep(waitInMs);
        throw new CustomException($"Task {id} throwing at {DateTime.UtcNow}");
    }

    static void Main(string[] args)
    {
        Task.Run(async () =>
        {
            await MyAmazingMethodAsync();
        }).Wait();

    }

    static async Task MyAmazingMethodAsync()
    {
        try
        {
            Task[] taskArray = { Task.Factory.StartNew(() => WaitAndThrow(1, 1000)),
                                 Task.Factory.StartNew(() => WaitAndThrow(2, 2000)),
                                 Task.Factory.StartNew(() => WaitAndThrow(3, 3000)) };

            Task.WaitAll(taskArray);
            //await Task.WhenAll(taskArray);
            Console.WriteLine("This isn't going to happen");
        }
        catch (AggregateException ex)
        {
            foreach (var inner in ex.InnerExceptions)
            {
                Console.WriteLine($"Caught AggregateException in Main at {DateTime.UtcNow}: " + inner.Message);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Caught Exception in Main at {DateTime.UtcNow}: " + ex.Message);
        }
        Console.WriteLine("Done.");
        Console.ReadLine();
    }
}
tymtam
  • 31,798
  • 8
  • 86
  • 126
  • 13
    Thanks for pointing this out. This explanation was useful in the scenario I'm currently working. Perhaps not the "biggest practical difference", but definitely a good call out. – Urk Apr 24 '18 at 19:25
  • The exception handling being biggest practical difference might be more applicable to the comparison between `await t1; await t2; await t3;` vs `await Task.WhenAll(t1,t2,t3);` – frostshoxx Mar 13 '19 at 15:51
  • 2
    Doesn't this exception behaviour contradict the docs here (https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.whenall?view=netframework-4.7.2#System_Threading_Tasks_Task_WhenAll__1_System_Collections_Generic_IEnumerable_System_Threading_Tasks_Task___0___) "If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state, where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks." – Dasith Wijes Apr 04 '19 at 12:29
  • 3
    I consider this to be an artifact of `await`, not a difference between the two methods. Both propagate an `AggregateException`, either throwing directly or through a property (the [`Task.Exception`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.exception) property). – Theodor Zoulias Mar 30 '20 at 05:43
39

As an example of the difference --
if you have a task that does something with the UI thread (e.g. a task that represents an animation in a Storyboard) if you Task.WaitAll() then the UI thread is blocked and the UI is never updated.
if you use await Task.WhenAll() then the UI thread is not blocked, and the UI will be updated.

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
J. Long
  • 401
  • 4
  • 2
  • That could ve avoided if you set ConfigureAwait(false); on the waited tasks. I won't recommend the use WaitAll, though – X.Otano Jan 19 '21 at 08:29
27

What do they do:

  • Internally both do the same thing.

What's the difference:

  • WaitAll is a blocking call
  • WhenAll - not - code will continue executing

Use which when:

  • WaitAll when cannot continue without having the result
  • WhenAll when what just to be notified, not blocked
i100
  • 4,529
  • 1
  • 22
  • 20
  • 2
    @MartinRhodes But what if you don't await it immediately, but continue with some other work and _then_ await it? You don't have that possibility with `WaitAll` as I understand it. – Jeppe Mar 31 '19 at 14:59
  • @Jeppe Wouldn't you just differ the call to `Task.WaitAll` _after_ you did your some other work? I mean, instead of calling it right after starting your tasks. – P-L Dec 23 '19 at 20:36