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?
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.
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).
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.
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.
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.
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();
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With