WaitAll vs WhenAll

335

В чем разница между Task.WaitAll()и Task.WhenAll()из Async CTP? Можете ли вы предоставить пример кода для иллюстрации различных вариантов использования?

Ярон Леви
источник

Ответы:

504

Task.WaitAll блокирует текущий поток, пока все не завершится.

Task.WhenAllвозвращает задачу, которая представляет действие ожидания, пока все не будет завершено.

Это означает, что из асинхронного метода вы можете использовать:

await Task.WhenAll(tasks);

... что означает, что ваш метод продолжится, когда все будет завершено, но вы не будете связывать поток, чтобы просто торчать до этого времени.

Джон Скит
источник
2
После долгих чтений становится ясно, что async не имеет ничего общего с потоками blog.stephencleary.com/2013/11/there-is-no-thread.html
Винс Пануччо
7
@ Vince: я думаю, что «ничего общего с потоками» не является преувеличением, и важно понимать, как асинхронные операции взаимодействуют с потоками.
Джон Скит
6
@KevinBui: Нет, он не должен его блокировать - он будет ждать задание, возвращаемое WhenAll, но это не то же самое, что блокирование потока.
Джон Скит,
1
@JonSkeet Возможно, точное различие между этими двумя слишком тонко для меня. Можете ли вы указать мне (и, возможно, остальным из нас) на какое-то упоминание, которое прояснит разницу?
CatShoes
125
@CatShoes: Не совсем - я объяснил это так хорошо, как могу. Полагаю, я мог бы привести аналогию - это как разница между заказом еды на вынос и остановкой у двери в ожидании прибытия, по сравнению с заказом еды на вынос, другими делами и открытием двери, когда прибывает курьер ...
Джон Скит
51

Хотя ответ JonSkeet объясняет разницу, как правило, превосходно, есть еще одно отличие: обработка исключений .

Task.WaitAllБросает, AggregateExceptionкогда выбрасывает любое из заданий, и вы можете проверить все сгенерированные исключения. Оператор awaitin await Task.WhenAllразворачивает AggregateExceptionи возвращает только первое исключение.

Когда программа, представленная ниже, выполняется с await Task.WhenAll(taskArray)выводом следующим образом.

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.

Когда нижеприведенная программа выполняется с Task.WaitAll(taskArray)выходом следующим образом.

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.

Программа:

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
источник
13
самое большое практическое отличие - обработка исключений. В самом деле? Потому что это не самая большая практическая разница. Самым большим практическим отличием является то, что одно - асинхронное и неблокирующее, а другое - блокирующее. Это гораздо важнее, чем то, как он обрабатывает исключения.
Лиам
5
Спасибо за указание на это. Это объяснение было полезно в сценарии, над которым я сейчас работаю. Возможно, не самая большая практическая разница, но определенно хороший призыв.
Urk
Регулируя существо наибольшего практического значения исключения может быть более применимо к сравнению await t1; await t2; await t3;противawait Task.WhenAll(t1,t2,t3);
frostshoxx
1
Разве это поведение исключения не противоречит документам здесь ( docs.microsoft.com/en-us/dotnet/api/… ) "Если любая из предоставленных задач завершается в состоянии сбоя, возвращенная задача также завершится в состоянии сбоя где его исключения будут содержать агрегацию набора развернутых исключений из каждой поставленной задачи. "
Dasith Wijes
1
Я считаю, что это артефакт await, а не разница между этими двумя методами. Оба распространяют AggregateException, либо выбрасывая напрямую, либо через свойство ( Task.Exceptionсвойство).
Теодор Зулиас
20

В качестве примера различия - если у вас есть задача, она делает что-то с потоком пользовательского интерфейса (например, задача, которая представляет анимацию в раскадровке), если вы Task.WaitAll()тогда поток пользовательского интерфейса блокируется, и пользовательский интерфейс никогда не обновляется. если вы используете, await Task.WhenAll()тогда поток пользовательского интерфейса не блокируется, и пользовательский интерфейс будет обновлен.

Дж. Лонг
источник
7

Что они делают:

  • Внутренне оба делают одно и то же.

Какая разница:

  • WaitAll - это блокирующий вызов
  • Когда все - не - код продолжит выполняться

Используйте, когда:

  • WaitAll, когда невозможно продолжить без результата
  • КогдаВсе когда то, что просто нужно уведомить, а не заблокировать
i100
источник
1
@MartinRhodes Но что, если вы не ждете этого сразу, а продолжаете какую-то другую работу, а потом ждете? У вас нет такой возможности, WaitAllкак я понимаю.
Jeppe
@Jeppe Не могли бы вы просто изменить вызов Task.WaitAll после того, как вы сделали свою другую работу? Я имею в виду, вместо того, чтобы называть это сразу после запуска ваших задач.
PL