whenall run parallel method español ejemplos create await async c# .net task-parallel-library async-await c#-5.0

c# - run - Usando async/await para múltiples tareas



task.run c# (5)

Estoy usando un cliente API que es completamente asíncrono, es decir, cada operación devuelve Task o Task<T> , 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); }

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

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());

Dado que el cliente API está utilizando HttpClient internamente, esperaría que esto emitiera 5 solicitudes HTTP de inmediato, escribiendo en la consola a medida que se completa cada una.


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


Puede usar la función Task.WhenAll que puede pasar n tareas; Task.WhenAll devolverá una tarea que se ejecutará hasta que se completen todas las tareas que haya pasado a Task.WhenAll . Tienes que esperar de forma asíncrona en Task.WhenAll para que no bloquees tu hilo de interfaz de usuario:

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 }


Tenía curiosidad por ver los resultados de los métodos proporcionados en la pregunta, así como la respuesta aceptada, por lo 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<Worker> { 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<Worker> workers, DateTime testStart) { Parallel.ForEach(workers, worker => worker.DoWork(testStart).Wait()); } static void PerformTest_TaskWaitAll(List<Worker> workers, DateTime testStart) { Task.WaitAll(workers.Select(worker => worker.DoWork(testStart)).ToArray()); } static Task PerformTest_TaskWhenAll(List<Worker> workers, DateTime testStart) { return Task.WhenAll(workers.Select(worker => worker.DoWork(testStart))); } } }

Y la salida 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.


Parallel.ForEach requiere una lista de trabajadores definidos por el usuario y una Action no asíncrona para realizar con cada trabajador.

Task.WaitAll y Task.WhenAll requieren una List<Task> , que por definición son asíncronas.

la response muy útil para entender la diferencia, pero necesita una corrección para Parallel.ForEach . No tiene la reputación suficiente para responder a su comentario, por lo tanto, mi propia respuesta.

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 void 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")); 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")); } public async Task DoWorkAsync(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<Worker> { 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<Worker> workers, DateTime testStart) { Parallel.ForEach(workers, worker => worker.DoWork(testStart)); } static void PerformTest_TaskWaitAll(List<Worker> workers, DateTime testStart) { Task.WaitAll(workers.Select(worker => worker.DoWorkAsync(testStart)).ToArray()); } static Task PerformTest_TaskWhenAll(List<Worker> workers, DateTime testStart) { return Task.WhenAll(workers.Select(worker => worker.DoWorkAsync(testStart))); } } }

La salida resultante está abajo. Los tiempos de ejecución son comparables. Hice esta prueba mientras mi computadora estaba realizando el análisis antivirus semanal. Cambiar el orden de las pruebas cambió los tiempos de ejecución en ellas.

Starting test: Parallel.ForEach... Worker 1 started on thread 9, beginning 0.02 seconds after test start. Worker 2 started on thread 10, beginning 0.02 seconds after test start. Worker 3 started on thread 11, beginning 0.02 seconds after test start. Worker 4 started on thread 13, beginning 0.03 seconds after test start. Worker 5 started on thread 14, beginning 0.03 seconds after test start. Worker 1 stopped; the worker took 1.00 seconds, and it finished 1.02 seconds after the test start. Worker 2 stopped; the worker took 2.00 seconds, and it finished 2.02 seconds after the test start. Worker 3 stopped; the worker took 3.00 seconds, and it finished 3.03 seconds after the test start. Worker 4 stopped; the worker took 4.00 seconds, and it finished 4.03 seconds after the test start. Worker 5 stopped; the worker took 5.00 seconds, and it finished 5.03 seconds after the test start. Test finished after 5.03 seconds. Starting test: Task.WaitAll... Worker 1 started on thread 9, beginning 0.00 seconds after test start. Worker 2 started on thread 9, beginning 0.00 seconds after test start. Worker 3 started on thread 9, beginning 0.00 seconds after test start. Worker 4 started on thread 9, beginning 0.00 seconds after test start. Worker 5 started on thread 9, 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 9, beginning 0.00 seconds after test start. Worker 2 started on thread 9, beginning 0.00 seconds after test start. Worker 3 started on thread 9, beginning 0.00 seconds after test start. Worker 4 started on thread 9, beginning 0.00 seconds after test start. Worker 5 started on thread 9, 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.01 seconds after the test start. Test finished after 5.01 seconds.


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 cuelga 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 subprocesos y sus subprocesos no tendrán la libertad de procesar ningún otro trabajo hasta que finalice la operación.

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 , simplemente 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 detallada del blog que analiza todas las alternativas y sus ventajas / desventajas: Cómo y dónde E / S asíncronas simultáneas con ASP.NET Web API