En caso de que no me preocupe el orden de finalización de la tarea y solo necesito que todos se completen, ¿debería usar en await Task.WhenAll
lugar de múltiples await
? por ejemplo, ¿está DoWork2
debajo de un método preferido para DoWork1
(y por qué?):
using System;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static async Task<string> DoTaskAsync(string name, int timeout)
{
var start = DateTime.Now;
Console.WriteLine("Enter {0}, {1}", name, timeout);
await Task.Delay(timeout);
Console.WriteLine("Exit {0}, {1}", name, (DateTime.Now - start).TotalMilliseconds);
return name;
}
static async Task DoWork1()
{
var t1 = DoTaskAsync("t1.1", 3000);
var t2 = DoTaskAsync("t1.2", 2000);
var t3 = DoTaskAsync("t1.3", 1000);
await t1; await t2; await t3;
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
static async Task DoWork2()
{
var t1 = DoTaskAsync("t2.1", 3000);
var t2 = DoTaskAsync("t2.2", 2000);
var t3 = DoTaskAsync("t2.3", 1000);
await Task.WhenAll(t1, t2, t3);
Console.WriteLine("DoWork2 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
static void Main(string[] args)
{
Task.WhenAll(DoWork1(), DoWork2()).Wait();
}
}
}
await t1; await t2; ....; await tn
=> el segundo es siempre la mejor opción en ambos casosRespuestas:
Sí, utilícelo
WhenAll
porque propaga todos los errores a la vez. Con las esperas múltiples, pierde errores si uno de los anteriores espera lanzamientos.Otra diferencia importante es que
WhenAll
esperará a que se completen todas las tareas incluso en presencia de fallas (tareas con fallas o canceladas). Esperar manualmente en secuencia provocaría una concurrencia inesperada porque la parte de su programa que quiere esperar en realidad continuará antes de tiempo.Creo que también facilita la lectura del código porque la semántica que desea está documentada directamente en el código.
fuente
await
su resultado.Tengo entendido que la razón principal para preferir
Task.WhenAll
múltiplesawait
s es el rendimiento / tarea "agitación": elDoWork1
método hace algo como esto:Por el contrario,
DoWork2
hace esto:Si esto es lo suficientemente importante para su caso particular es, por supuesto, "dependiente del contexto" (perdón por el juego de palabras).
fuente
Un método asincrónico se implementa como una máquina de estado. Es posible escribir métodos para que no se compilen en máquinas de estado, esto a menudo se conoce como un método asíncrono de vía rápida. Estos pueden implementarse así:
Al usarlo
Task.WhenAll
, es posible mantener este código de vía rápida mientras se garantiza que la persona que llama puede esperar a que se completen todas las tareas, por ejemplo:fuente
(Descargo de responsabilidad: esta respuesta está tomada / inspirada del curso TPL Async de Ian Griffiths sobre Pluralsight )
Otra razón para preferir WhenAll es el manejo de excepciones.
Suponga que tiene un bloque try-catch en sus métodos DoWork, y suponga que están llamando a diferentes métodos DoTask:
En este caso, si las 3 tareas arrojan excepciones, solo se capturará la primera. Cualquier excepción posterior se perderá. Es decir, si t2 y t3 arrojan una excepción, solo se capturará t2; Las excepciones de tareas posteriores pasarán desapercibidas.
Donde como en WhenAll: si falla una o todas las tareas, la tarea resultante contendrá todas las excepciones. La palabra clave de espera siempre vuelve a arrojar la primera excepción. Entonces, las otras excepciones aún no se observan de manera efectiva. Una forma de superar esto es agregar una continuación vacía después de la tarea WhenAll y poner la espera allí. De esta manera, si la tarea falla, la propiedad del resultado arrojará la Excepción agregada completa:
fuente
Las otras respuestas a esta pregunta ofrecen razones técnicas por las cuales
await Task.WhenAll(t1, t2, t3);
se prefiere. Esta respuesta tendrá como objetivo mirarlo desde un lado más suave (al que @usr alude) sin dejar de llegar a la misma conclusión.await Task.WhenAll(t1, t2, t3);
Es un enfoque más funcional, ya que declara intención y es atómico.Con
await t1; await t2; await t3;
, no hay nada que impida que un compañero de equipo (¡o incluso tu futuro yo mismo!) Agregue código entre lasawait
declaraciones individuales . Claro, lo has comprimido en una línea para lograr eso, pero eso no resuelve el problema. Además, generalmente es una mala forma en un entorno de equipo incluir varias declaraciones en una línea de código dada, ya que puede hacer que el archivo fuente sea más difícil de escanear para los ojos humanos.En pocas palabras,
await Task.WhenAll(t1, t2, t3);
es más fácil de mantener, ya que comunica su intención con mayor claridad y es menos vulnerable a errores peculiares que pueden surgir de actualizaciones bien intencionadas del código, o incluso simplemente se fusiona mal.fuente