Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running async methods in parallel

I've got an async method, GetExpensiveThing(), which performs some expensive I/O work. This is how I am using it:

// Serial execution public async Task<List<Thing>> GetThings() {     var first = await GetExpensiveThing();     var second = await GetExpensiveThing();     return new List<Thing>() { first, second }; } 

But since it's an expensive method, I want to execute these calls in in parallel. I would have thought moving the awaits would have solved this:

// Serial execution public async Task<List<Thing>> GetThings() {     var first = GetExpensiveThing();     var second = GetExpensiveThing();     return new List<Thing>() { await first, await second }; } 

That didn't work, so I wrapped them in some tasks and this works:

// Parallel execution public async Task<List<Thing>> GetThings() {     var first = Task.Run(() =>     {         return GetExpensiveThing();     });      var second = Task.Run(() =>     {         return GetExpensiveThing();     });      return new List<Thing>() { first.Result, second.Result }; } 

I even tried playing around with awaits and async in and around the tasks, but it got really confusing and I had no luck.

Is there a better to run async methods in parallel, or are tasks a good approach?

like image 537
Dave New Avatar asked Jul 28 '16 10:07

Dave New


People also ask

Do async tasks run in parallel?

There is no parallelism here, as the “async Task” does not automatically make something run in in parallel. This will spawn 2 threads, run them simultaneously, and return when both threads are done. This will create a list of Tasks to be run at the same time.

How do I run async in parallel?

In order to run multiple async/await calls in parallel, all we need to do is add the calls to an array, and then pass that array as an argument to Promise. all() . Promise. all() will wait for all the provided async calls to be resolved before it carries on(see Conclusion for caveat).

Is async await parallel C#?

With TPL we can implement Parallel Programming in C# . NET very easy. Async and Await keywords were introduced in C# 5.0 by Microsoft. When you use the “Async” keyword, you can write code the same way you wrote synchronous code.

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.


2 Answers

Is there a better to run async methods in parallel, or are tasks a good approach?

Yes, the "best" approach is to utilize the Task.WhenAll method. However, your second approach should have ran in parallel. I have created a .NET Fiddle, this should help shed some light. Your second approach should actually be running in parallel. My fiddle proves this!

Consider the following:

public Task<Thing[]> GetThingsAsync() {     var first = GetExpensiveThingAsync();     var second = GetExpensiveThingAsync();      return Task.WhenAll(first, second); } 

Note

It is preferred to use the "Async" suffix, instead of GetThings and GetExpensiveThing - we should have GetThingsAsync and GetExpensiveThingAsync respectively - source.

like image 147
David Pine Avatar answered Oct 08 '22 03:10

David Pine


Task.WhenAll() has a tendency to become unperformant with large scale/amount of tasks firing simultaneously - without moderation/throttling.

If you are doing a lot of tasks in a list and wanting to await the final outcome, then I propose using a partition with a limit on the degree of parallelism.

I have modified Stephen Toub's blog elegant approach to modern LINQ:

public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> funcBody, int maxDoP = 4) {     async Task AwaitPartition(IEnumerator<T> partition)     {         using (partition)         {             while (partition.MoveNext())             {                  await Task.Yield(); // prevents a sync/hot thread hangup                  await funcBody(partition.Current);             }         }     }      return Task.WhenAll(         Partitioner             .Create(source)             .GetPartitions(maxDoP)             .AsParallel()             .Select(p => AwaitPartition(p))); } 

How it works is simple, take an IEnumerable - dissect it into evenish partitions and the fire a function/method against each element, in each partition, at the same time. No more than one element in each partition at anyone time, but n Tasks in n partitions.

Extension Usage:

await myList.ParallelForEachAsync(myFunc, Environment.ProcessorCount);

Edit: I now keep some overloads in a repository on Github if you need more options. It's in a NuGet too for NetStandard.

Edit 2: Thanks to comments from Theodor below, I was able to mitigate poorly written Async Tasks from blocking parallelism by using await Task.Yield();.

like image 29
HouseCat Avatar answered Oct 08 '22 02:10

HouseCat