Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using async/await for multiple tasks

I'm using an API client that is completely asynchrounous, that is, each operation either returns Task or Task<T>, e.g:

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

Using the C# 5 async/await operators, what is the correct/most efficient way to start multiple tasks and wait for them all to complete:

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

or:

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

Since the API client is using HttpClient internally, I would expect this to issue 5 HTTP requests immediately, writing to the console as each one completes.

like image 246
Ben Foster Avatar asked Sep 09 '12 08:09

Ben Foster


People also ask

Can async method have multiple awaits?

For more information, I have an async / await intro on my blog. So additionally, if a method with multiple awaits is called by a caller, the responsibility for finishing every statement of that method is with the caller.

Does async await improve performance?

C# Language Async-Await Async/await will only improve performance if it allows the machine to do additional work.

How do you wait for multiple tasks?

The correct way to await multiple tasks is the Task. WhenAll method: await Task. WhenAll(first, second); . Then you can await them individually to get their results, because you know that all have completed successfully.

Can two async functions run at the same time?

You can call multiple asynchronous functions without awaiting them. This will execute them in parallel. While doing so, save the returned promises in variables, and await them at some point either individually or using Promise. all() and process the results.


2 Answers

I was curious to see the results of the methods provided in the question as well as the accepted answer, so I put it to the test.

Here's the code:

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

And the resulting output:

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. 
like image 27
RiaanDP Avatar answered Oct 23 '22 07:10

RiaanDP


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

Although you run the operations in parallel with the above code, this code blocks each thread that each operation runs on. For example, if the network call takes 2 seconds, each thread hangs for 2 seconds w/o doing anything but waiting.

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

On the other hand, the above code with WaitAll also blocks the threads and your threads won't be free to process any other work till the operation ends.

Recommended Approach

I would prefer WhenAll which will perform your operations asynchronously in Parallel.

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

In fact, in the above case, you don't even need to await, you can just directly return from the method as you don't have any continuations:

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

To back this up, here is a detailed blog post going through all the alternatives and their advantages/disadvantages: How and Where Concurrent Asynchronous I/O with ASP.NET Web API

like image 109
tugberk Avatar answered Oct 23 '22 08:10

tugberk