Дано три завдання - 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>