parallel - task.run c#
¿Por qué debería preferir single ''aguardar Tarea.Cuando todo'' en múltiples espera? (5)
(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.
Supongamos que tiene un bloque try-catch en sus métodos DoWork, y suponga que estaban llamando a diferentes métodos DoTask:
static async Task DoWork1() // modified with try-catch
{
try
{
var t1 = DoTask1Async("t1.1", 3000);
var t2 = DoTask2Async("t1.2", 2000);
var t3 = DoTask3Async("t1.3", 1000);
await t1; await t2; await t3;
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
catch (Exception x)
{
// ...
}
}
En este caso, si las 3 tareas generan excepciones, solo se capturará la primera. Cualquier excepción posterior se perderá. Es decir, si t2 y t3 arrojan una excepción, solo t2 quedará atrapado; etc. Las excepciones de tareas posteriores pasarán inadvertidas.
Donde, como en el caso de WhenAll - si alguna o todas las tareas tienen un error, la tarea resultante contendrá todas las excepciones. La palabra clave await todavía arroja siempre la primera excepción. Entonces las otras excepciones aún no se observan efectivamente. 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 forma, si la tarea falla, la propiedad result arrojará la Excepción total agregada:
static async Task DoWork2() //modified to catch all exceptions
{
try
{
var t1 = DoTask1Async("t1.1", 3000);
var t2 = DoTask2Async("t1.2", 2000);
var t3 = DoTask3Async("t1.3", 1000);
var t = Task.WhenAll(t1, t2, t3);
await t.ContinueWith(x => { });
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t.Result[0], t.Result[1], t.Result[2]));
}
catch (Exception x)
{
// ...
}
}
En caso de que no me preocupe el orden de finalización de la tarea y solo necesito que completen todas, ¿debo seguir utilizando la await Task.WhenAll
vez de múltiple await
? Por ejemplo, DoWord2
está 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();
}
}
}
Las otras respuestas a esta pregunta ofrecen razones técnicas por las cuales await Task.WhenAll(t1, t2, t3);
se prefiere. Esta respuesta tratará de verlo desde un lado más suave (al cual @usr alude) mientras se llega 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;
await t1; await t2; await t3;
, no hay nada que impida que un compañero de equipo (¡o tal vez incluso tu ser futuro!) agregue código entre las declaraciones individuales en await
. Claro, lo has comprimido en una línea para lograrlo, pero eso no resuelve el problema. Además, generalmente es mala forma en una configuración de equipo incluir múltiples 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 ojos humanos.
En pocas palabras, await Task.WhenAll(t1, t2, t3);
es más fácil de mantener, ya que comunica su intención de forma más clara y es menos vulnerable a los errores peculiares que pueden surgir de las actualizaciones bien intencionadas del código, o incluso solo las fusiones que salen mal.
Sí, use WhenAll
porque propaga todos los errores a la vez. Con la espera múltiple, se pierden los errores si uno de los primeros espera lanza.
Otra diferencia importante es que WhenAll esperará a que se completen todas las tareas. Una cadena de await
abortará la espera en la primera excepción, pero la ejecución de las tareas no esperadas continúa. Esto causa concurrencia inesperada.
Creo que también hace que sea más fácil leer el código porque la semántica que desea está directamente documentada en el código.
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 asincrónico de vía rápida. Estos se pueden implementar de esta manera:
public Task DoSomethingAsync()
{
return DoSomethingElseAsync();
}
Cuando se utiliza Task.WhenAll
. Task.WhenAll
que sea posible mantener este código de vía rápida sin dejar de garantizar que la persona que llama pueda esperar a que se completen todas las tareas, por ejemplo:
public Task DoSomethingAsync()
{
var t1 = DoTaskAsync("t2.1", 3000);
var t2 = DoTaskAsync("t2.2", 2000);
var t3 = DoTaskAsync("t2.3", 1000);
return Task.WhenAll(t1, t2, t3);
}
Task.WhenAll
entendido, la razón principal para preferir la Task.WhenAll
a la await
el rendimiento / la tarea "se DoWork1
": el método DoWork1
hace algo como esto:
- comenzar con un context dado
- guardar el contexto
- espera por t1
- restaurar el contexto original
- guardar el contexto
- espera por t2
- restaurar el contexto original
- guardar el contexto
- espera por t3
- restaurar el contexto original
Por el contrario, DoWork2
hace esto:
- comenzar con un contexto dado
- guardar el contexto
- espera por todos los t1, t2 y t3
- restaurar el contexto original
Si este es un acuerdo lo suficientemente grande para su caso particular es, por supuesto, "dependiente del contexto" (perdón por el juego de palabras).