Problem
I have three responsibilities:
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
They must all run before my code can continue, and I require the results of each. There isn’t a single item in common among the outcomes.
How do I place a call and wait for the three tasks to be completed before receiving the results?
Asked by Ian Vink
Solution #1
After you’ve used WhenAll, you may use await: to extract the results one by one.
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
Task.Result is another option (since you know by this point they have all completed successfully). However, I like to use await because it is plainly accurate, whereas Result can pose issues in other situations.
Answered by Stephen Cleary
Solution #2
Simply wait for each of the three jobs after you’ve completed them all.
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
Answered by Servy
Solution #3
You can use a helpful wrapper function like this if you’re using C# 7…
public static class TaskEx
{
public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
{
return (await task1, await task2);
}
}
When you wish to wait on numerous tasks with various return types, you can use syntax like this. Of course, you’d have to create numerous overloads for various quantities of tasks to anticipate.
var (someInt, someString) = await TaskEx.WhenAll(GetIntAsync(), GetStringAsync());
If you want to make this example into something actual, see Marc Gravell’s answer for some optimizations around ValueTask and already-completed tasks.
Answered by Joel Mueller
Solution #4
There are two intriguing instances when three jobs – FeedCat(), SellHouse(), and BuyCar() – are given: either they all complete synchronously (for whatever reason, perhaps caching or an error), or they don’t.
Let’s imagine we’ve gotten the following from the question:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// what here?
}
Now, let’s take a simple approach:
Task.WhenAll(x, y, z);
However, that is inconvenient for processing the results; we would normally want to wait:
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
await Task.WhenAll(x, y, z);
// presumably we want to do something with the results...
return DoWhatever(x.Result, y.Result, z.Result);
}
However, this adds a lot of overhead by allocating numerous arrays and lists (including the params Task[] array) (internally). It works, but it isn’t perfect, in my opinion. In many instances, it’s easier to utilize an async operation and simply wait for each one to complete:
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// do something with the results...
return DoWhatever(await x, await y, await z);
}
Contrary to some of the comments above, using await instead of Task. WhenAll makes no difference to how the tasks run (concurrently, sequentially, etc). At the highest level, Task. WhenAll predates good compiler support for async/await, and was useful when those things didn’t exist. It is also useful when you have an arbitrary array of tasks, rather than 3 discreet tasks.
However, the problem of async/await generating a lot of compiler noise for the continuation remains. If the jobs are likely to complete synchronously, we can improve performance by including a synchronous path with an asynchronous fallback:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await x, await y, await z);
}
This “synchronous path with async fallback” method is becoming more popular, particularly in high-performance programming where synchronous completions are widespread. It’s worth noting that if the completion is always actually asynchronous, it won’t assist.
Here are a few more things to consider:
Answered by Marc Gravell
Solution #5
You can save them in tasks and then wait for them all at once:
var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();
await Task.WhenAll(catTask, houseTask, carTask);
Cat cat = await catTask;
House house = await houseTask;
Car car = await carTask;
Answered by Reed Copsey
Post is based on https://stackoverflow.com/questions/17197699/awaiting-multiple-tasks-with-different-results