В случае, если меня не волнует порядок выполнения задач и мне просто нужно, чтобы они все выполнялись, следует ли мне использовать await Task.WhenAll
вместо нескольких await
? например, DoWork2
ниже предпочтительный метод DoWork1
(и почему?):
using System;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static async Task<string> DoTaskAsync(string name, int timeout)
{
var start = DateTime.Now;
Console.WriteLine("Enter {0}, {1}", name, timeout);
await Task.Delay(timeout);
Console.WriteLine("Exit {0}, {1}", name, (DateTime.Now - start).TotalMilliseconds);
return name;
}
static async Task DoWork1()
{
var t1 = DoTaskAsync("t1.1", 3000);
var t2 = DoTaskAsync("t1.2", 2000);
var t3 = DoTaskAsync("t1.3", 1000);
await t1; await t2; await t3;
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
static async Task DoWork2()
{
var t1 = DoTaskAsync("t2.1", 3000);
var t2 = DoTaskAsync("t2.2", 2000);
var t3 = DoTaskAsync("t2.3", 1000);
await Task.WhenAll(t1, t2, t3);
Console.WriteLine("DoWork2 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
static void Main(string[] args)
{
Task.WhenAll(DoWork1(), DoWork2()).Wait();
}
}
}
await t1; await t2; ....; await tn
=> второй всегда лучший выбор в обоих случаяхОтветы:
Да, использовать,
WhenAll
потому что он распространяет сразу все ошибки. При использовании нескольких ожиданий вы теряете ошибки, если одно из предыдущих ожиданий выбрасывает.Еще одно важное отличие - это
WhenAll
ожидание завершения всех задач даже при наличии сбоев (сбойных или отмененных задач). Последовательное ожидание вручную может вызвать неожиданный параллелизм, потому что часть вашей программы, которая хочет ждать, фактически продолжит работу раньше.Я думаю, что это также упрощает чтение кода, потому что нужная вам семантика прямо задокументирована в коде.
источник
await
его результат.Насколько я понимаю, основной причиной предпочтения
Task.WhenAll
множественныхawait
s является "перемешивание" производительности / задачи:DoWork1
метод делает что-то вроде этого:Напротив,
DoWork2
делает это:Достаточно ли это для вашего конкретного случая, конечно, "контекстно-зависимый" (простите за каламбур).
источник
Асинхронный метод реализован в виде конечного автомата. Можно написать методы так, чтобы они не компилировались в конечные автоматы, это часто называют ускоренным асинхронным методом. Их можно реализовать так:
При использовании
Task.WhenAll
можно сохранить этот код ускоренного отслеживания, при этом гарантируя, что вызывающий абонент сможет дождаться завершения всех задач, например:источник
(Отказ от ответственности: этот ответ взят / вдохновлен асинхронным курсом Яна Гриффитса по TPL на Pluralsight )
Еще одна причина предпочесть WhenAll - обработка исключений.
Предположим, у вас есть блок try-catch для ваших методов DoWork, и предположим, что они вызывают разные методы DoTask:
В этом случае, если все 3 задачи выдают исключения, будет обнаружена только первая. Любое последующее исключение будет потеряно. Т.е. если t2 и t3 вызывают исключение, будет перехвачен только t2; и т.д. Последующие исключения задач останутся незамеченными.
Где, как в WhenAll - в случае сбоя какой-либо или всех задач итоговая задача будет содержать все исключения. Ключевое слово await по-прежнему всегда повторно вызывает первое исключение. Таким образом, другие исключения по-прежнему остаются незамеченными. Один из способов решить эту проблему - добавить пустое продолжение после задачи WhenAll и поместить туда ожидание. Таким образом, если задача не удалась, свойство result выдаст полное исключение Aggregate Exception:
источник
Другие ответы на этот вопрос предлагают технические причины, почему
await Task.WhenAll(t1, t2, t3);
это предпочтение. Этот ответ будет направлен на то, чтобы взглянуть на это с более мягкой стороны (на которую ссылается @usr), но при этом прийти к тому же выводу.await Task.WhenAll(t1, t2, t3);
- это более функциональный подход, поскольку он декларирует намерение и является атомарным.При
await t1; await t2; await t3;
этом ничто не мешает товарищу по команде (или, возможно, даже вам в будущем!) Добавлять код между отдельнымиawait
операторами. Конечно, для этого вы сжали его до одной строки, но это не решает проблемы. Кроме того, обычно плохим тоном в командной настройке включать несколько операторов в заданную строку кода, так как это может затруднить сканирование исходного файла человеческим глазом.Проще говоря,
await Task.WhenAll(t1, t2, t3);
он более удобен в обслуживании, поскольку он более четко передает ваши намерения и менее уязвим для специфических ошибок, которые могут возникнуть из-за благонамеренных обновлений кода или даже просто неудачных слияний.источник