Tengo 3 tareas:
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
Todos deben ejecutarse antes de que mi código pueda continuar y también necesito los resultados de cada uno. Ninguno de los resultados tiene nada en común
¿Cómo llamo y espero a que se completen las 3 tareas y luego obtengo los resultados?
c#
.net
async-await
task-parallel-library
.net-4.5
Ian Vink
fuente
fuente
Respuestas:
Después de usar
WhenAll
, puede extraer los resultados individualmente conawait
:También puede usar
Task.Result
(ya que sabe en este punto, todos se han completado con éxito). Sin embargo, recomiendo usarloawait
porque es claramente correcto, aunqueResult
puede causar problemas en otros escenarios.fuente
WhenAll
de esto por completo; Las esperas se encargarán de garantizar que no pases de las 3 tareas posteriores hasta que se completen todas las tareas.Task.WhenAll()
permite ejecutar la tarea en modo paralelo . No puedo entender por qué @Servy ha sugerido eliminarlo. Sin elWhenAll
se ejecutarán uno por unocatTask
ya se está ejecutando cuando regresaFeedCat
. Entonces, cualquiera de los enfoques funcionará: la única pregunta es si los quiereawait
uno a la vez o todos juntos. El manejo de errores es ligeramente diferente: si lo usaTask.WhenAll
, lo haráawait
a todos, incluso si uno de ellos falla antes.WhenAll
no tiene impacto sobre cuándo se ejecutan las operaciones o cómo se ejecutan. Es solamente tiene ninguna posibilidad de efectuar la forma en que se observan los resultados. En este caso particular, la única diferencia es que un error en uno de los dos primeros métodos daría lugar a que la excepción se lanzara en esta pila de llamadas antes en mi método que el de Stephen (aunque siempre se lanzaría el mismo error, si hubiera )Solo
await
las tres tareas por separado, después de iniciarlas todas.fuente
Task.WhenAll
cambios literalmente nada sobre el comportamiento del programa, de ninguna manera observable. Es una llamada al método puramente redundante. Puede agregarlo, si lo desea, como una opción estética, pero no cambia lo que hace el código. El tiempo de ejecución del código será idéntico con o sin esa llamada al método (bueno, técnicamente habrá una sobrecarga muy pequeña para llamarWhenAll
, pero esto debería ser insignificante), solo haciendo que esa versión sea un poco más larga para ejecutarse que esta versión.WhenAll
es un cambio puramente estético. La única diferencia observable en el comportamiento es si espera a que finalicen las tareas posteriores si falla una tarea anterior, lo que generalmente no es necesario hacer. Si no cree en las numerosas explicaciones de por qué su afirmación no es verdadera, simplemente puede ejecutar el código usted mismo y ver que no es cierto.Si está usando C # 7, puede usar un práctico método de envoltura como este ...
... para habilitar una sintaxis conveniente como esta cuando desea esperar en múltiples tareas con diferentes tipos de retorno. Tendría que hacer múltiples sobrecargas para diferentes números de tareas a la espera, por supuesto.
Sin embargo, vea la respuesta de Marc Gravell para algunas optimizaciones sobre ValueTask y tareas ya completadas si tiene la intención de convertir este ejemplo en algo real.
fuente
Task.WhenAll()
no está devolviendo una tupla. Una se está construyendo a partir de lasResult
propiedades de las tareas proporcionadas después de que la tarea devuelta seTask.WhenAll()
complete..Result
llamadas según el razonamiento de Stephen para evitar que otras personas perpetúen la mala práctica copiando su ejemplo.Dadas tres tareas
FeedCat()
,SellHouse()
yBuyCar()
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:
Ahora, un enfoque simple sería:
pero ... eso no es conveniente para procesar los resultados; normalmente nos gustaría
await
eso: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 unaasync
operación y soloawait
cada una a la vez: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 paraasync
/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: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:preferir
ValueTask<T>
aTask<T>
si hay una buena probabilidad de cosas siempre totalmente sincronizada con muchos diferentes valores de retorno:si es posible, prefieren
IsCompletedSuccessfully
aStatus == TaskStatus.RanToCompletion
; esto ahora existe en .NET Core paraTask
, y en todas partes paraValueTask<T>
fuente
Task
cuando todos están hechos sin usar los resultados.await
obtener la semántica de "mejor" excepción, suponiendo que las excepciones son raras pero significativasPuede almacenarlos en tareas y luego esperarlos a todos:
fuente
var catTask = FeedCat()
ejecuta la funciónFeedCat()
y almacena el resultado paracatTask
hacer que laawait Task.WhenAll()
parte sea inútil ya que el método ya se ejecutó.En caso de que esté tratando de registrar todos los errores, asegúrese de mantener Task. Cuando toda la línea en su código, muchos comentarios sugieren que puede eliminarlo y esperar tareas individuales. Task.WhenAll es realmente importante para el manejo de errores. Sin esta línea, posiblemente deje su código abierto para excepciones no observadas.
Imagine FeedCat lanza una excepción en el siguiente código:
En ese caso, nunca esperará en houseTask ni en carTask. Hay 3 escenarios posibles aquí:
SellHouse ya se completó correctamente cuando FeedCat falló. En este caso estás bien.
SellHouse no está completo y falla con la excepción en algún momento. La excepción no se observa y se volverá a generar en el subproceso finalizador.
SellHouse no está completo y contiene espera dentro de él. En caso de que su código se ejecute en ASP.NET, SellHouse fallará tan pronto como algunas de las esperas se completen dentro de él. Esto sucede porque básicamente hizo que se disparara y olvidara el contexto de llamada y sincronización se perdió tan pronto como FeedCat falló.
Aquí hay un error que obtendrá para el caso (3):
Para el caso (2) obtendrá un error similar pero con el seguimiento de la pila de excepción original.
Para .NET 4.0 y versiones posteriores, puede detectar excepciones no observadas utilizando TaskScheduler.UnobservedTaskException. Para .NET 4.5 y posteriores, las excepciones no observadas se tragan de manera predeterminada para .NET 4.0 la excepción no observada bloqueará su proceso.
Más detalles aquí: Manejo de excepciones de tareas en .NET 4.5
fuente
Puede usar
Task.WhenAll
como se mencionó oTask.WaitAll
, dependiendo de si desea que el hilo espere. Eche un vistazo al enlace para obtener una explicación de ambos.WaitAll vs WhenAll
fuente
Use
Task.WhenAll
y luego espere los resultados:fuente
Advertencia hacia adelante
Solo un aviso rápido para aquellos que visitan este y otros subprocesos similares que buscan una forma de paralelizar EntityFramework usando el conjunto de herramientas de tarea async + await + : el patrón que se muestra aquí es sólido, sin embargo, cuando se trata del copo de nieve especial de EF, no lo hará lograr la ejecución paralela a menos y hasta que use una instancia de contexto db (nueva) separada dentro de cada una de las llamadas * Async () involucradas.
Este tipo de cosas es necesario debido a las limitaciones de diseño inherentes a los contextos ef-db que prohíben ejecutar múltiples consultas en paralelo en la misma instancia de contexto ef-db.
Aprovechando las respuestas ya dadas, esta es la forma de asegurarse de que recopila todos los valores incluso en el caso de que una o más de las tareas den como resultado una excepción:
Una implementación alternativa que tiene más o menos las mismas características de rendimiento podría ser:
fuente
si desea acceder a Cat, haga esto:
Esto es muy simple de hacer y muy útil de usar, no hay necesidad de buscar una solución compleja.
fuente
dynamic
es el diablo. Es para interoperabilidad COM complicada y tal, y no debe usarse en ninguna situación donde no sea absolutamente necesario. Particularmente si te importa el rendimiento. O escriba seguridad. O refactorizando. O depuración.