ChatGPT解决这个技术问题 Extra ChatGPT

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 ?


A
Alberto Solano

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.


After much reading, It is clear that async has nothing to do with threads blog.stephencleary.com/2013/11/there-is-no-thread.html
@Vince: I think "nothing to do with threads" is an overstatement, and it's important to understand how async operations interact with threads.
@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.
@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: 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...
t
tymtam

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();
    }
}

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.
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);
Doesn't this exception behaviour contradict the docs here (docs.microsoft.com/en-us/dotnet/api/…) "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."
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 property).
L
Liam

As an example of the difference -- if you have a task the 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.


That could ve avoided if you set ConfigureAwait(false); on the waited tasks. I won't recommend the use WaitAll, though
i
i100

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


@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 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.
J
Jeremy Caney

The Task.WaitAll blocks the current thread. It will remain blocked until all other tasks have completed execution. It has a void return value. The Task.WhenAll method returns a Task<TResult[]>. It is used to create a task that will complete if and only if all the other tasks have completed.

When to use which?

About the only time I use Task.WaitAll is inside a non-async function (that must remain non-async), that I want to add concurrency to. However, BE WARNED: this can lead to deadlock since it blocks the current thread.

With that in mind, anytime you can convert a function to async, do so, and use Task.WhenAll, with an await on it. This is definitely the preferred approach.

Exceptions

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. In both cases, all tasks will run, even if one of them throws an exception.