Dadas tres tareas FeedCat()
, SellHouse()
y BuyCar()
hay dos casos interesantes: o todos se completan sincrónicamente (por alguna razón, tal vez el almacenamiento en caché o un error), o no lo hacen.
Digamos que tenemos, de la pregunta:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// what here?
}
Ahora, un enfoque simple sería:
Task.WhenAll(x, y, z);
pero ... eso no es conveniente para procesar los resultados; normalmente nos gustaría await
eso:
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);
}
pero esto genera muchos gastos generales y asigna varias matrices (incluida la params Task[]
matriz) y listas (internamente). Funciona, pero no es una gran OMI. En muchos sentidos, es más simple usar una async
operación y solo await
cada una a la vez:
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);
}
Contrariamente a algunos de los comentarios anteriores, el uso en await
lugar de noTask.WhenAll
hace ninguna diferencia en la forma en que se ejecutan las tareas (simultáneamente, secuencialmente, etc.). En el nivel más alto, Task.WhenAll
es anterior al buen soporte del compilador para async
/ await
, y fue útil cuando esas cosas no existían . También es útil cuando tiene una variedad arbitraria de tareas, en lugar de 3 tareas discretas.
Pero: todavía tenemos el problema de que async
/ await
genera mucho ruido de compilación para la continuación. Si es probable que las tareas podrían en realidad completar de forma sincrónica, entonces podemos optimizar esto mediante la construcción de una ruta síncrona con un repliegue asíncrona:
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);
}
Este enfoque de "ruta de sincronización con recuperación asíncrona" es cada vez más común, especialmente en el código de alto rendimiento donde las terminaciones síncronas son relativamente frecuentes. Tenga en cuenta que no ayudará en absoluto si la finalización siempre es genuinamente asíncrona.
Cosas adicionales que se aplican aquí:
con C # reciente, un patrón común es que el async
método alternativo se implementa comúnmente como una función local:
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);
}
preferir ValueTask<T>
a Task<T>
si hay una buena probabilidad de cosas siempre totalmente sincronizada con muchos diferentes valores de retorno:
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);
}
si es posible, prefieren IsCompletedSuccessfully
a Status == TaskStatus.RanToCompletion
; esto ahora existe en .NET Core para Task
, y en todas partes paraValueTask<T>