Даны три задачи - FeedCat()
, SellHouse()
иBuyCar()
, есть два интересных случая: либо они все полные синхронно (по какой - то причине, возможно кэширование или ошибка), или они не делают.
Допустим, у нас есть, из вопроса:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// what here?
}
Теперь простой подход будет следующим:
Task.WhenAll(x, y, z);
но ... это не удобно для обработки результатов; мы обычно хотели бы к await
этому:
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
await Task.WhenAll(x, y, z);
// presumably we want to do something with the results...
return DoWhatever(x.Result, y.Result, z.Result);
}
но это приводит к большим накладным расходам и выделяет различные массивы (включая params Task[]
массив) и списки (внутри). Это работает, но это не великое ИМО. Во многих отношениях проще использовать async
операцию и только await
каждую по очереди:
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// do something with the results...
return DoWhatever(await x, await y, await z);
}
Вопреки некоторым комментариям выше, использование await
вместо того, чтобы не Task.WhenAll
иметь никакого значения к тому, как выполняются задачи (одновременно, последовательно и т. Д.). На самом высоком уровне Task.WhenAll
предшествует хорошей поддержке компилятора для async
/ await
, и была полезна, когда таких вещей не было . Это также полезно, когда у вас есть произвольный массив задач, а не 3 дискретных задачи.
Но: у нас все еще есть проблема, которая async
/ await
генерирует много шума компилятора для продолжения. Если есть вероятность , что задачи , возможно , на самом деле выполнить синхронно, то мы можем оптимизировать это путем создания в синхронном пути с асинхронным запасным вариантом:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await x, await y, await z);
}
Такой подход «синхронизация с асинхронным резервом» становится все более распространенным, особенно в высокопроизводительном коде, где синхронные завершения встречаются относительно часто. Обратите внимание, что это совсем не поможет, если завершение всегда действительно асинхронное.
Дополнительные вещи, которые применяются здесь:
в недавнем C # общий шаблон для async
резервного метода обычно реализуется как локальная функция:
Task<string> DoTheThings() {
async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await a, await b, await c);
}
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
предпочитает , ValueTask<T>
чтобы , Task<T>
если есть хороший шанс, когда - либо полностью синхронно с множеством различных возвращаемых значений:
ValueTask<string> DoTheThings() {
async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await a, await b, await c);
}
ValueTask<Cat> x = FeedCat();
ValueTask<House> y = SellHouse();
ValueTask<Tesla> z = BuyCar();
if(x.IsCompletedSuccessfully &&
y.IsCompletedSuccessfully &&
z.IsCompletedSuccessfully)
return new ValueTask<string>(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
если это возможно, предпочитают , IsCompletedSuccessfully
чтобы Status == TaskStatus.RanToCompletion
; теперь это существует в .NET Core для Task
и везде дляValueTask<T>