Usando async / await para múltiples tareas

Estoy usando un cliente API que es completamente asincrónico, es decir, cada operación devuelve Task o Task , por ejemplo:

 static async Task DoSomething(int siteId, int postId, IBlogClient client) { await client.DeletePost(siteId, postId); // call API client Console.WriteLine("Deleted post {0}.", siteId); } 

Al utilizar los operadores asíncrono / en espera C # 5, ¿cuál es la forma correcta / más eficiente de iniciar múltiples tareas y esperar a que se completen todas?

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait()); 

o:

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray()); 

Como el cliente API está utilizando HttpClient internamente, esperaría que se emitan 5 solicitudes HTTP de inmediato, escribiendo en la consola a medida que se completa.

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait()); 

Aunque ejecuta las operaciones en paralelo con el código anterior, este código bloquea cada subproceso en el que se ejecuta cada operación. Por ejemplo, si la llamada a la red tarda 2 segundos, cada hilo se bloquea durante 2 segundos sin hacer nada más que esperar.

 int[] ids = new[] { 1, 2, 3, 4, 5 }; Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray()); 

Por otro lado, el código anterior con WaitAll también bloquea los hilos y tus hilos no podrán procesar ningún otro trabajo hasta que la operación finalice.

Enfoque recomendado

Preferiría WhenAll que realizará sus operaciones de forma asíncrona en Paralelo.

 public async Task DoWork() { int[] ids = new[] { 1, 2, 3, 4, 5 }; await Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient))); } 

De hecho, en el caso anterior, ni siquiera necesita await , puede regresar directamente del método ya que no tiene ninguna continuación:

 public Task DoWork() { int[] ids = new[] { 1, 2, 3, 4, 5 }; return Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient))); } 

Para respaldar esto, aquí hay una publicación de blog detallada que analiza todas las alternativas y sus ventajas / desventajas: Cómo y dónde E / S simultáneas concurrentes con ASP.NET Web API

Tenía curiosidad de ver los resultados de los métodos proporcionados en la pregunta, así como la respuesta aceptada, así que lo puse a prueba.

Aquí está el código:

 using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsyncTest { class Program { class Worker { public int Id; public int SleepTimeout; public async Task DoWork(DateTime testStart) { var workerStart = DateTime.Now; Console.WriteLine("Worker {0} started on thread {1}, beginning {2} seconds after test start.", Id, Thread.CurrentThread.ManagedThreadId, (workerStart-testStart).TotalSeconds.ToString("F2")); await Task.Run(() => Thread.Sleep(SleepTimeout)); var workerEnd = DateTime.Now; Console.WriteLine("Worker {0} stopped; the worker took {1} seconds, and it finished {2} seconds after the test start.", Id, (workerEnd-workerStart).TotalSeconds.ToString("F2"), (workerEnd-testStart).TotalSeconds.ToString("F2")); } } static void Main(string[] args) { var workers = new List { new Worker { Id = 1, SleepTimeout = 1000 }, new Worker { Id = 2, SleepTimeout = 2000 }, new Worker { Id = 3, SleepTimeout = 3000 }, new Worker { Id = 4, SleepTimeout = 4000 }, new Worker { Id = 5, SleepTimeout = 5000 }, }; var startTime = DateTime.Now; Console.WriteLine("Starting test: Parallel.ForEach..."); PerformTest_ParallelForEach(workers, startTime); var endTime = DateTime.Now; Console.WriteLine("Test finished after {0} seconds.\n", (endTime - startTime).TotalSeconds.ToString("F2")); startTime = DateTime.Now; Console.WriteLine("Starting test: Task.WaitAll..."); PerformTest_TaskWaitAll(workers, startTime); endTime = DateTime.Now; Console.WriteLine("Test finished after {0} seconds.\n", (endTime - startTime).TotalSeconds.ToString("F2")); startTime = DateTime.Now; Console.WriteLine("Starting test: Task.WhenAll..."); var task = PerformTest_TaskWhenAll(workers, startTime); task.Wait(); endTime = DateTime.Now; Console.WriteLine("Test finished after {0} seconds.\n", (endTime - startTime).TotalSeconds.ToString("F2")); Console.ReadKey(); } static void PerformTest_ParallelForEach(List workers, DateTime testStart) { Parallel.ForEach(workers, worker => worker.DoWork(testStart).Wait()); } static void PerformTest_TaskWaitAll(List workers, DateTime testStart) { Task.WaitAll(workers.Select(worker => worker.DoWork(testStart)).ToArray()); } static Task PerformTest_TaskWhenAll(List workers, DateTime testStart) { return Task.WhenAll(workers.Select(worker => worker.DoWork(testStart))); } } } 

Y el resultado resultante:

 Starting test: Parallel.ForEach... Worker 1 started on thread 1, beginning 0.21 seconds after test start. Worker 4 started on thread 5, beginning 0.21 seconds after test start. Worker 2 started on thread 3, beginning 0.21 seconds after test start. Worker 5 started on thread 6, beginning 0.21 seconds after test start. Worker 3 started on thread 4, beginning 0.21 seconds after test start. Worker 1 stopped; the worker took 1.90 seconds, and it finished 2.11 seconds after the test start. Worker 2 stopped; the worker took 3.89 seconds, and it finished 4.10 seconds after the test start. Worker 3 stopped; the worker took 5.89 seconds, and it finished 6.10 seconds after the test start. Worker 4 stopped; the worker took 5.90 seconds, and it finished 6.11 seconds after the test start. Worker 5 stopped; the worker took 8.89 seconds, and it finished 9.10 seconds after the test start. Test finished after 9.10 seconds. Starting test: Task.WaitAll... Worker 1 started on thread 1, beginning 0.01 seconds after test start. Worker 2 started on thread 1, beginning 0.01 seconds after test start. Worker 3 started on thread 1, beginning 0.01 seconds after test start. Worker 4 started on thread 1, beginning 0.01 seconds after test start. Worker 5 started on thread 1, beginning 0.01 seconds after test start. Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.01 seconds after the test start. Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.01 seconds after the test start. Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.01 seconds after the test start. Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.01 seconds after the test start. Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.01 seconds after the test start. Test finished after 5.01 seconds. Starting test: Task.WhenAll... Worker 1 started on thread 1, beginning 0.00 seconds after test start. Worker 2 started on thread 1, beginning 0.00 seconds after test start. Worker 3 started on thread 1, beginning 0.00 seconds after test start. Worker 4 started on thread 1, beginning 0.00 seconds after test start. Worker 5 started on thread 1, beginning 0.00 seconds after test start. Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.00 seconds after the test start. Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.00 seconds after the test start. Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.00 seconds after the test start. Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.00 seconds after the test start. Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.00 seconds after the test start. Test finished after 5.00 seconds. 

Como la API que está llamando es asíncrona, la versión de Parallel.ForEach no tiene mucho sentido. No .Wait usar. .Wait en la versión de WaitAll ya que eso perdería el paralelismo. Otra alternativa si el que llama es asincrónico está usando Task.WhenAll después de hacer Select y ToArray para generar el conjunto de tareas. Una segunda alternativa es usar Rx 2.0

Puede usar Task.WhenAll . Task.WhenAll funciones pueden pasar n tareas; Task.WhenAll devolverá una tarea que se ejecuta hasta su finalización cuando todas las tareas que pasó a Task.WhenAll complete. Tienes que esperar asíncronamente en Task.WhenAll para que no bloquees tu subproceso de UI:

  public async Task DoSomeThing() { var Task[] tasks = new Task[numTasks]; for(int i = 0; i < numTask; i++) { tasks[i] = CallSomeAsync(); } await Task.WhenAll(tasks); // code that'll execute on UI thread }