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