Usando async / wait para várias tarefas

Estou usando um cliente API completamente asynchronous, ou seja, cada operação retorna Task ou Task , por exemplo:

 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 os operadores async / wait do C # 5, qual é a maneira correta / mais eficiente de iniciar várias tarefas e esperar que todas sejam concluídas:

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

ou:

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

Como o cliente da API está usando o HttpClient internamente, eu esperaria que isso emitisse 5 solicitações HTTP imediatamente, gravando no console à medida que cada um fosse concluído.

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

    Embora você execute as operações em paralelo com o código acima, esse código bloqueia cada thread em que cada operação é executada. Por exemplo, se a chamada de rede leva 2 segundos, cada thread trava por 2 segundos sem fazer nada, mas espera.

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

    Por outro lado, o código acima com WaitAll também bloqueia os threads e seus threads não estarão livres para processar qualquer outro trabalho até que a operação termine.

    Abordagem Recomendada

    Eu preferiria WhenAll que executará suas operações de forma assíncrona em paralelo.

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

    De fato, no caso acima, você nem precisa await , você pode simplesmente retornar diretamente do método, pois você não tem nenhuma continuação:

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

    Para fazer isso, aqui está uma postagem detalhada no blog, passando por todas as alternativas e suas vantagens / desvantagens: Como e onde a E / S assíncrona simultânea com a API da Web do ASP.NET

    Eu estava curioso para ver os resultados dos methods fornecidos na pergunta, bem como a resposta aceita, então eu coloquei isso em teste.

    Aqui está o 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))); } } } 

    E a saída 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 a API que você está chamando é assíncrona, a versão Parallel.ForEach não faz muito sentido. Você não deve usar .Wait na versão WaitAll pois perderia o paralelismo Outra alternativa se o chamador for asynchronous está usando Task.WhenAll depois de fazer Select e ToArray para gerar a matriz de tarefas. Uma segunda alternativa é usar o Rx 2.0

    Você pode usar Task.WhenAll function que você pode passar n tarefas; Task.WhenAll retornará uma tarefa que é executada até a conclusão quando todas as tarefas passadas para Task.WhenAll concluídas. Você tem que esperar de forma assíncrona em Task.WhenAll para que você não bloqueie seu thread de interface do usuário:

      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 }