425

I have 3 tasks:

private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}

They all need to run before my code can continue and I need the results from each as well. None of the results have anything in common with each other

How do I call and await for the 3 tasks to complete and then get the results?

2

13 Answers 13

738

After you use WhenAll, you can pull the results out individually with await:

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;

[Note that asynchronous methods always return "hot" (already started) tasks.]

You can also use Task.Result (since you know by this point they have all completed successfully). However, I recommend using await because it's clearly correct, while Result can cause problems in other scenarios.

Sign up to request clarification or add additional context in comments.

37 Comments

You can just remove the WhenAll from this entirely; the awaits will take care of ensuring you don't move past the 3 later assignments until the tasks are all completed.
Task.WhenAll() allows to run the task in parallel mode. I can't understand why @Servy has suggested to remove it. Without the WhenAll they will be run one by one
@Sergey: The tasks begin executing immediately. E.g., catTask is already running by the time it's returned from FeedCat. So either approach will work - the only question is whether you want to await them one at a time or all together. The error handling is slightly different - if you use Task.WhenAll, then it will await them all, even if one of them fails early.
@Sergey Calling WhenAll has no impact on when the operations execute, or how they execute. It only has any possibility of effecting how the results are observed. In this particular case, the only difference is that an error in one of the first two methods would result in the exception being thrown in this call stack earlier in my method than Stephen's (although the same error would always be thrown, if there are any).
@Sergey: The key is that asynchronous methods always return "hot" (already started) tasks.
|
175

Just await the three tasks separately, after starting them all:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

Note: In case an exception is thrown by any of the tasks, this code will potentially return the exception before later tasks have finished, but they'll all run. In pretty much all situations not waiting when you already know the result is desirable. In fringe situations, it might not be.

36 Comments

@Bargitta No, that's false. They'll do their work in parallel. Feel free to run it and see for yourself.
People keep asking the same question after years... I feel it is important to stress again that a task "starts on create" in the body of the answer: maybe they don't bother reading comments
@StephenYork Adding Task.WhenAll changes literally nothing about the behavior of the program, in any observable way. It is a purely redundant method call. You're welcome to add it, if you like to, as an aesthetic choice, but it does not change what the code does. The execution time of the code will be identical with or without that method call (well, technically there'll be a really small overhead for calling WhenAll, but this should be negligible), only making that version slightly longer to run than this version.
@StephenYork Your example runs the operations sequentially for two reasons. Your asynchronous methods aren't actually asynchronous, they're synchronous. The fact that you have synchronous methods that always return already completed tasks prevents them from running concurrently. Next, you don't actually do what is shown in this answer of starting all three asynchronous methods, and then awaiting the three tasks in turn. Your example doesn't call each method until the previous has finished, thus explicitly preventing one from being started until the previous has finished, unlike this code.
@MarcvanNieuwenhuijzen That's demonstrably not true, as has been discussed in the comments here, and on other answers. Adding WhenAll is a purely aesthetic change. The only observable difference in behavior is whether you wait for later tasks to finish if an earlier task faults, which there typically isn't a need to do. If you don't believe the numerous explanations for why your statement isn't true, you can simply run the code for yourself and see that it's not true.
|
65

If you're using C# 7, you can use a handy wrapper method like this...

public static class TaskEx
{
    public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
    {
        return (await task1, await task2);
    }
}

...to enable convenient syntax like this when you want to wait on multiple tasks with different return types. You'd have to make multiple overloads for different numbers of tasks to await, of course.

var (someInt, someString) = await TaskEx.WhenAll(GetIntAsync(), GetStringAsync());

However, see Marc Gravell's answer for some optimizations around ValueTask and already-completed tasks if you intend to turn this example into something real.

8 Comments

@YuryShcherbakov Task.WhenAll() isn't returning a tuple. One is being constructed from the Result properties of the provided tasks after the task returned by Task.WhenAll() completes.
I'd suggest replacing the .Result calls as per Stephen's reasoning to avoid other people perpetuating the bad practice by copying your example.
I wonder why this method isn't this part of the framework? It seems so useful. Did they run out of time and have to stop at a single return type?
@nrofis That's false. Both tasks are created and therefore started before either is awaited. It's equivalent to Servy's answer.
In case task2 will fail with exception, than task 1 will fail with exception, you will never catch exception from task2, it will be unobserved. In case you are logging errors using try catch unobserved exception will never appear in your logs. You can add call to await Task.WhenAll(task1, task2) before return to address this.
|
40

Given three tasks - FeedCat(), SellHouse() and BuyCar(), there are two interesting cases: either they all complete synchronously (for some reason, perhaps caching or an error), or they don't.

Let's say we have, from the question:

Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();
    // what here?
}

Now, a simple approach would be:

Task.WhenAll(x, y, z);

but ... that isn't convenient for processing the results; we'd typically want to await that:

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

but this does lots of overhead and allocates various arrays (including the params Task[] array) and lists (internally). It works, but it isn't great IMO. In many ways it is simpler to use an async operation and just await each in turn:

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 discrete tasks.

But: we still have the problem that async/await generates a lot of compiler noise for the continuation. If it is likely that the tasks might actually complete synchronously, then we can optimize this by building in 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 "sync path with async fallback" approach is increasingly common especially in high performance code where synchronous completions are relatively frequent. Note it won't help at all if the completion is always genuinely asynchronous.

Additional things that apply here:

  1. with recent C#, a common pattern is for the async fallback method is commonly implemented as a local function:

     Task<string> DoTheThings() {
         async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
             return DoWhatever(await a, await b, await c);
         }
         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);
     }
    
  2. prefer ValueTask<T> to Task<T> if there is a good chance of things ever completely synchronously with many different return values:

     ValueTask<string> DoTheThings() {
         async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) {
             return DoWhatever(await a, await b, await c);
         }
         ValueTask<Cat> x = FeedCat();
         ValueTask<House> y = SellHouse();
         ValueTask<Tesla> z = BuyCar();
    
         if(x.IsCompletedSuccessfully &&
            y.IsCompletedSuccessfully &&
            z.IsCompletedSuccessfully)
             return new ValueTask<string>(
               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);
     }
    
  3. if possible, prefer IsCompletedSuccessfully to Status == TaskStatus.RanToCompletion; this now exists in .NET Core for Task, and everywhere for ValueTask<T>

10 Comments

"Contrary to various answers here, using await instead of Task.WhenAll makes no difference to how the tasks run (concurrently, sequentially, etc)" I don't see any answer that say that. I'd have already commented on them saying as much if they did. There are lots of comments on lots of answers saying that, but no answers. Which are you referring to? Also note that your answer doesn't handle the result of the tasks (or deal with the fact that the results are all of a different type). You have composed them in a method that just returns a Task when they're all done without using the results.
@Servy you're right, that was comments; I'll add a tweak to show using the results
@Servy that is a complex topic - you get different exception semantics from the two scenarios - awaiting to trigger an exception behaves differently than accessing .Result to trigger the exception. IMO at that point we should await to get the "better" exception semantics, on the assumption that exceptions are rare but meaningful
@Almis what makes you think that it is running synchronously? do you have a minimal runnable code? here's what I have, and it says "async": gist.github.com/mgravell/8e08a7d3dbf3bbfc17ff46c494a59150
@MarcGravell I've read your answer a bit more carefully and realized that I misunderstood your statement about task completing synchronously. What I don't understand is that you say Task.WhenAll makes no difference. But I do see a clear difference between Task.WhenAll and awaiting in each iteration. If I create 10 awaits with 500 ms delays and launch them together with Task.WhenAll they complete within less than a second. Whereas if I await for each 10 awaits - they are performed sequentially (just as I have expected) and complete within ~5 seconds.
|
18

In case you are trying to log all errors make sure you keep Task.WhenAll line in your code, lot of comments suggest that you can remove it and wait for individual tasks. Task.WhenAll is really important for error handling. Without this line you potentially leaving your code open for unobserved exceptions.

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;

Imagine FeedCat throws exception in the following code:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

In that case you will never await on houseTask nor carTask. There are 3 possible scenarios here:

  1. SellHouse is already completed successfully when FeedCat failed. In this case you are fine.

  2. SellHouse is not complete and fails with exception at some point. Exception is not observed and will be rethrown on finalizer thread.

  3. SellHouse is not complete and contains awaits inside it. In case your code runs in ASP.NET SellHouse will fail as soon as some of the awaits will completed inside it. This happens because you basically made fire & forget call and synchronization context was lost as soon as FeedCat failed.

Here is error that you will get for case (3):

System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
   at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
   at System.Threading.Tasks.Task.Execute()
   --- End of inner exception stack trace ---
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
   at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
   at System.Threading.Tasks.Task.Execute()<---

For case (2) you will get similar error but with original exception stack trace.

For .NET 4.0 and later you can catch unobserved exceptions using TaskScheduler.UnobservedTaskException. For .NET 4.5 and later unobserved exceptions are swallowed by default for .NET 4.0 unobserved exception will crash your process.

More details here: Task Exception Handling in .NET 4.5

3 Comments

This should be upvoted. I am surprised that all that discussion above is saying that Task.WhenAll and awaiting separately is equivalent. It is not. If any task throws an exception, the other tasks may not complete or you may lose errors from them. As my scenario involved error handling for all of my tasks and retrying on some in case an error is thrown, my code did not work as expected until I added Task.WhenAll. Thank you for pointing this out.
Why do you await the tasks after the call to await Task.Whenall? Would it not be better to just call catTask.Result for example since the await operator generates unnecessary code in the state machine? After the await Task.Whenall I can safely use the Result property safely or am I misunderstanding something?
You are right, it is safe to call .Result here. We only reach individual task awaits when all tasks are completed and there were no errors in each task. So await and .Result will produce the same results, and .Results will have fewer instructions. However in general when you see .Result in the code, this is a trigger to check if deadlock issue and error handling were addressed. So unless code is not performance critical, I would stick with await. More info here - stackoverflow.com/a/24657079/463640
13

You can store them in tasks, then await them all:

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;

8 Comments

doesn't var catTask = FeedCat() execute the function FeedCat() and store the result into catTask making the await Task.WhenAll() part kind of useless since the method has already executed ??
@sanuel if they return task <t>, then no... they start the async open, but don't wait for it
I don't think this is accurate, please see the discussions under @StephenCleary's answer... also see Servy's answer.
if I need to add .ConfigrtueAwait(false). Would I add it to just Task.WhenAll or to each awaiter that follows?
@user44 They will run in parallel - without the WhenAll, you may get the Cat result before house is done, etc (which may or may not be important).
|
6

You can use Task.WhenAll as mentioned, or Task.WaitAll, depending on whether you want the thread to wait. Take a look at the link for an explanation of both.

WaitAll vs WhenAll

Comments

6

Forward Warning

Just a quick headsup to those visiting this and other similar threads looking for a way to parallelize EntityFramework using async+await+task tool-set: The pattern shown here is sound, however, when it comes to the special snowflake of EF you will not achieve parallel execution unless and until you use a separate (new) db-context-instance inside each and every *Async() call involved.

This sort of thing is necessary due to inherent design limitations of ef-db-contexts which forbid running multiple queries in parallel in the same ef-db-context instance.


Capitalizing on the answers already given, this is the way to make sure that you collect all values even in the case that one or more of the tasks results in an exception:

  public async Task<string> Foobar() {
    async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
        return DoSomething(await a, await b, await c);
    }

    using (var carTask = BuyCarAsync())
    using (var catTask = FeedCatAsync())
    using (var houseTask = SellHouseAsync())
    {
        if (carTask.Status == TaskStatus.RanToCompletion //triple
            && catTask.Status == TaskStatus.RanToCompletion //cache
            && houseTask.Status == TaskStatus.RanToCompletion) { //hits
            return Task.FromResult(DoSomething(catTask.Result, carTask.Result, houseTask.Result)); //fast-track
        }

        cat = await catTask;
        car = await carTask;
        house = await houseTask;
        //or Task.AwaitAll(carTask, catTask, houseTask);
        //or await Task.WhenAll(carTask, catTask, houseTask);
        //it depends on how you like exception handling better

        return Awaited(catTask, carTask, houseTask);
   }
 }

An alternative implementation that has more or less the same performance characteristics could be:

 public async Task<string> Foobar() {
    using (var carTask = BuyCarAsync())
    using (var catTask = FeedCatAsync())
    using (var houseTask = SellHouseAsync())
    {
        cat = catTask.Status == TaskStatus.RanToCompletion ? catTask.Result : (await catTask);
        car = carTask.Status == TaskStatus.RanToCompletion ? carTask.Result : (await carTask);
        house = houseTask.Status == TaskStatus.RanToCompletion ? houseTask.Result : (await houseTask);

        return DoSomething(cat, car, house);
     }
 }

Comments

0

Use Task.WhenAll and then await the results:

var tCat = FeedCat();
var tHouse = SellHouse();
var tCar = BuyCar();
await Task.WhenAll(tCat, tHouse, tCar);
Cat cat = await tCat;
House house = await tHouse;
Tesla car = await tCar; 
//as they have all definitely finished, you could also use Task.Value.

1 Comment

mm...not Task.Value (maybe it used to exist in 2013?), rather tCat.Result, tHouse.Result or tCar.Result
0

The three tasks in your example differ greatly in importance. In case one of them fails, you probably want to know what happened with the others. For example in case the communication with the automatic cat feeder failed, you don't want to miss whether selling your house succeeded or failed. So it makes sense to return back not just a Cat, a House and a Tesla, but the tasks themselves. The calling code will then be able to query separately each of the three tasks, and react appropriately to their successful or failed completions:

public async Task<(Task<Cat>, Task<House>, Task<Tesla>)> FeedCatSellHouseBuyCar()
{
    Task<Cat> task1 = FeedCat();
    Task<House> task2 = SellHouse();
    Task<Tesla> task3 = BuyCar();

    // All three tasks are launched at this point.

    await Task.WhenAll(task1, task2, task3)
        .ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);

    // All three tasks are completed at this point.
    
    return (task1, task2, task3);
}

Usage example:

var (catTask, houseTask, teslaTask) = await FeedCatSellHouseBuyCar();

// All three tasks are completed at this point.

if (catTask.IsCompletedSuccessfully)
    Console.WriteLine($"{catTask.Result.Name} is eating her healthy meal.");
else
    Console.WriteLine("Your cat is starving!");

if (houseTask.IsCompletedSuccessfully)
    Console.WriteLine($"Your house at {houseTask.Result.Address} was sold. You are now rich and homeless!");
else
    Console.WriteLine("You are still the poor owner of your house.");

if (teslaTask.IsCompletedSuccessfully)
    Console.WriteLine($"You are now the owner a battery-powered {teslaTask.Result.Name}.");
else
    Console.WriteLine("You are still driving a Hyundai.");

For a version that does not depend on the .NET 8 API ConfigureAwaitOptions.SuppressThrowing, see the 3rd revision of this answer.

Comments

0

While writing a compiler I became interested in syntax for awaiting multiple tasks (and getting their values).

For example:

var (employees, books) = await [
   dbConnection.GetAllAsync<Employee>(),
   dbConnection.GetAllAsync<Book>(),
];

Then I realized that the following syntax, almost identical, is already possible in C#:

var (books, employees) = await Tasks(
   dbConnection.GetAllAsync<Books>(),
   dbConnection.GetAllAsync<Employees>());

If you're interested, see the NuGet package AwaitMultiple. It can be used like this up to 16 arguments.

1 Comment

I posted feedback to your library here.
-2
var dn = await Task.WhenAll<dynamic>(FeedCat(),SellHouse(),BuyCar());

if you want to access Cat, you do this:

var ct = (Cat)dn[0];

This is very simple to do and very useful to use, there is no need to go after a complex solution.

1 Comment

There's just one problem with this: dynamic is the devil. It's for tricky COM interop and such, and should not be used in any situation where it's not absolutely needed. Particularly if you care about performance. Or type safety. Or refactoring. Or debugging.
-3

isnt the await statment making the code to run in sequential order? consider the following code

class Program
{
    static Stopwatch _stopwatch = new();

    static async Task Main(string[] args)
    {
        Console.WriteLine($"fire hot");
        _stopwatch.Start();
        var carTask = BuyCar();
        var catTask = FeedCat();
        var houseTask = SellHouse();
        await carTask;
        await catTask;
        await houseTask;
        Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} done!");

        Console.WriteLine($"using await");
        _stopwatch.Restart();
        await BuyCar();
        await FeedCat();
        await SellHouse();            

        Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} done!");
    }

    static async Task BuyCar()
    {
        Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} buy car started");
        await Task.Delay(2000);
        Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} buy car done");
    }

    static async Task FeedCat()
    {
        Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} feed cat started");
        await Task.Delay(1000);
        Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} feed cat done");
    }

    static async Task SellHouse()
    {
        Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} sell house started");
        await Task.Delay(10);
        Console.WriteLine($"{_stopwatch.ElapsedMilliseconds} sell house done");
    }
}

fire hot
0 buy car started
3 feed cat started
4 sell house started
18 sell house done
1004 feed cat done
2013 buy car done
2014 done!
using await
0 buy car started
2012 buy car done
2012 feed cat started
3018 feed cat done
3018 sell house started
3033 sell house done
3034 done!

1 Comment

I believe with these awaits it will run synchronously which probably isn't what you want.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.