387

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?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Ian Vink
  • 66,960
  • 104
  • 341
  • 555
  • 37
    Do you have any ordering requirement? That is, do you want to not sell the house until after the cat is fed? – Eric Lippert Jun 19 '13 at 17:53
  • Relevant question: [Why should I prefer single 'await Task.WhenAll' over multiple awaits?](https://stackoverflow.com/questions/18310996/why-should-i-prefer-single-await-task-whenall-over-multiple-awaits) – Theodor Zoulias Jan 03 '23 at 15:37

12 Answers12

676

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.

Wayne Uroda
  • 5,025
  • 4
  • 30
  • 35
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 129
    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. – Servy Jun 19 '13 at 17:45
  • 13
    True, though `WhenAll` has a more explicit intent. – Stephen Cleary Jun 19 '13 at 18:00
  • I'm a bit confused here. I get the same behavior with or without the WhenAll and even if I replace it with a WhenAny. – Antao Almada Feb 23 '15 at 10:40
  • For the WhenAny case I was missing an if IsCompleted for each task. The use of cascading awaits is confusing. When would Result fail? In the case of an exception thrown? – Antao Almada Feb 23 '15 at 10:50
  • 5
    In the case of a faulted task, `Result` will wrap the original exception within an `AggregateException`, complicating error handling. – Stephen Cleary Feb 23 '15 at 13:13
  • 243
    `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 – SerjG Mar 13 '15 at 12:07
  • 148
    @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. – Stephen Cleary Mar 13 '15 at 12:17
  • 44
    @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). – Servy Mar 16 '15 at 14:05
  • 5
    @Stephen agree with you. I wanted to mention that if you have 3 tasks for 3, 4 and 5 seconds respectively `WhenAll` will work 5 seconds. On the other hand running them BY CHAIN will be for the 3+4+5=12 seconds. As someone said: tasks should not be related to each other for the `WhenAll`. That's why I think `WhenAll` has a major influence for the performance for example. – SerjG Mar 17 '15 at 15:14
  • 2
    @Servy read my previous comment. Running "by chain" and running "in parallel" mode are completely different. In the `WhenAll` they will be run IMMEDIATELY together. Without it they will be run one after the previous one. `await; + await; == await.ContinueWith(await;);` – SerjG Mar 17 '15 at 15:20
  • 21
    @Sergey: It would only be 12 seconds if you awaited each one before calling the next method. If you structure the code like Servy's answer, it would only be 5 seconds. – Stephen Cleary Mar 17 '15 at 15:21
  • 8
    @Sergey Go ahead and actually run the code for yourself and time it if you don't believe Stephen. You don't have to take our word for it. – Servy Mar 17 '15 at 15:21
  • 26
    @Servy: I was wring guys, sorry. I couldn't imagine that such syntax could be **parallel**. Absolutely confusing for me – SerjG Mar 17 '15 at 18:36
  • 61
    @Sergey: The key is that asynchronous methods always return "hot" (already started) tasks. – Stephen Cleary Mar 17 '15 at 19:02
  • @Stephen Can you explain why we need another await instead of pulling result directly? Also what is better approach using Task.WhenAll or just awaiting them as mentioned by @Servy? – Aleksander Bethke Oct 16 '15 at 14:20
  • 2
    @AleksanderBethke Stephen and I have already covered the very minor differences between the two options *in this same comment thread*. If any of those differences are relevant to you, then choose accordingly. If none of those differences matter in your situation (which is the most like option) then it's simply a personal preference. – Servy Oct 16 '15 at 14:24
  • @Sergey is right, whenAll is running in parallel way, await in chain is waiting one by one. The running time tells truth. – Steven Zack Oct 26 '16 at 17:17
  • 1
    I would add `.ConfigureAwait(false)` to the `WhenAll(tasks)` call to keep from blocking the thread. It would be the most optimal way to run this parallelization. – Dagrooms Nov 17 '16 at 19:59
  • Using await Task.WhenAll, if FeedCat tasks 10 seconds, SellHouse 3 seconds and BuyCar 2 seconds, once SellHouse and BuyCar are done, are those 2 Tasks released while waiting for FeedCat or it's only the "main" thread that is released while waiting for the 3 Tasks to be done? – vinhent Feb 09 '17 at 16:55
  • @Vinhent: I'm not sure what you mean by "released". Those tasks are complete, if that's what you mean. – Stephen Cleary Feb 09 '17 at 18:34
  • @Stephen Cleary Released and return to the thread pool so they can serve new request. Because if one of the 3 Tasks take way more time than the 2 others, will I waste 2 thread waiting for the third one to complete ? – vinhent Feb 09 '17 at 18:56
  • 8
    @Vinhent: Those tasks are not threads. They're Promise Tasks, not Delegate Tasks. So there's no taking them from the thread pool in the first place. – Stephen Cleary Feb 09 '17 at 20:01
  • Is it possible to write code allowing you to proceed based on whichever one returns first? – codeMonkey Jul 04 '17 at 00:54
  • 2
    @codeMonkey or anyone else with this issue, see: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/start-multiple-async-tasks-and-process-them-as-they-complete – amcc Mar 15 '18 at 23:40
  • 5
    Here's an example which proves `Task.WhenAll` isn't needed. https://gist.github.com/aherrick/239647bf24bd6483924ce6a22f5c8edf – aherrick Feb 02 '19 at 13:51
  • 6
    @aherrick: Right. As I explain above, the error handling is a bit different without the `WhenAll`. But the main reason that I generally include it is so that the code is *clearer*. It's easier for someone unfamiliar with it to understand the intent and semantics of the method, and therefore the code is easier to maintain. – Stephen Cleary Feb 02 '19 at 23:05
  • 2
    WhenAll is useful when we have tasks created in foreach loop – Gen.L Apr 22 '19 at 18:24
  • 2
    If you explicitly call `await Task.WhenAll(catTask, houseTask, carTask);`, wouldn't it be a better choice then to call `catTask.Result` rather than a misleading `await`? @StephenCleary, wouldn't any potential exception, as you mention, be thrown when the `.WhenAll` completes and thus there is no harm in using `.Wait` after `await Task.WhenAll`? – Patrick Szalapski Apr 23 '19 at 21:06
  • 5
    @PatrickSzalapski: Code using `await` is more resilient to refactoring, since it cannot accidentally become blocking code. Let me turn your question around. Given that `await` is the *natural* way of consuming a task, what advantage does `Result`/`Wait` have over the more natural `await`? – Stephen Cleary Apr 23 '19 at 23:52
  • 5
    When I see an `await`, I assume it is necessary to wait for an perhaps-unfinished task to return. In our case, the waiting already happened on the `.WhenAll` line, so the result is guaranteed to be there, synchronously. (You wouldn 't want to call `.Wait`, only `.Result`; please pardon my typo.) – Patrick Szalapski Apr 24 '19 at 21:42
  • 2
    @RichardCollette It's not a thread blocker on a task that you know to be complete (because you already awaited Task.WhenAll): "Once the result of an operation is available, it is stored and is returned immediately on subsequent calls to the Result property." – Joel Mueller Nov 09 '19 at 22:32
  • 1
    2022 is calling, what is the conclusion now? Does .WhenAll make a difference? should we just call await or WhenAll+await ? Does WhenAll run in parallel? (Stephen vs. Serj) ? – juFo May 18 '22 at 13:43
  • 4
    @juFo: My answer is still what I would say today . – Stephen Cleary May 18 '22 at 14:00
  • 1
    @StephenCleary I've read through this comment thread 3 times - it's still not clear to me what you think `AwaitAll` accomplishes? @aherrick posted an [example](https://gist.github.com/aherrick/239647bf24bd6483924ce6a22f5c8edf) proving you don't need this. In my opinion, [this](https://stackoverflow.com/a/17197745/283851) should be the accepted answer. – mindplay.dk Aug 01 '22 at 07:47
  • 5
    @mindplay.dk the `await Task.WhenAll(catTask, houseTask, carTask);` ensures that in case the `catTask` fails, the other two tasks will not become fire-and-forget. [Fire-and-forget](https://stackoverflow.com/questions/61316504/proper-way-to-start-and-async-fire-and-forget-call/61320933#61320933) is bad. The `await Task.WhenAll` is not an optional luxury. It's a requirement for correct program behavior. – Theodor Zoulias Aug 01 '22 at 08:14
  • 3
    @TheodorZoulias you're right, I see now - using `Task.WhenAll` ensures that all tasks succeed or fail as a whole. Thanks for removing my edit! To others struggling to understand this, you can try commenting-out the `WhenAll` in [this example](https://dotnetfiddle.net/pZuEyh) and see the difference in the console output. – mindplay.dk Aug 02 '22 at 15:28
  • 2
    @juFo There was never any question whether or not it runs parallel with/without `WhenAll`. It was a flaw of logic on Serj's part to assume otherwise. It's also a bit disturbing that so many upvoted it without instead just sitting back for 2 minutes to logically consider what is happening in both scenarios. He should've deleted that comment as soon as he realized he was wrong to avoid misleading so many people. – arkon Sep 25 '22 at 01:35
159

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.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • 16
    @Bargitta No, that's false. They'll do their work in parallel. Feel free to run it and see for yourself. – Servy Oct 11 '16 at 13:00
  • 14
    [People](http://stackoverflow.com/questions/44001825/awaiting-tasks-of-different-types/) 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](http://stackoverflow.com/a/44002657/6996876): maybe they don't bother reading comments –  May 17 '17 at 07:24
  • 1
    @user1892538 There's only one sentence in the answer, and it already says that... – Servy May 17 '17 at 13:09
  • 2
    @Servy it doesn't say that at all. It says "Just await the three tasks separately, after starting them all." It doesn't explicitly say that "creating the task starts it". – claudekennilol Sep 27 '17 at 17:18
  • The code in this answer DOES return the .Result and each task is awaited one by one. To run these concurrently and make the most of the benefit of 'async' programming then it's best to use Task.WhenAll(catTask, houseTask, carTask) then call the results via the .Result property on each. This will ensure the shortest possible run time to complete all operations. – Stephen York Oct 25 '17 at 21:14
  • 18
    @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. – Servy Oct 25 '17 at 21:22
  • 1
    @StephenYork The simplest way to see that is to simply write up a sample program and see what actually happens when you run it, with and without the `WhenAll` in it. That will let you see for yourself what executes and when. – Servy Oct 25 '17 at 21:44
  • @Servy I actually did take the time to write a test app before posting here and the results show that you are in fact incorrect. Run my code (in a separate answer on the SO question) and you can see the point being demonstrated. I have three methods, first sleeping for 5 seconds, then another for 1 second and another for 3 seconds. Executing as you suggest awaiting each one the total run time is 9 seconds. You're ademant that using WhenAll(one, two, three) has absolutely no difference and it's just syntactic sugar, yet in fact the total run time is 5 seconds. You are wrong. – Stephen York Oct 25 '17 at 23:57
  • 9
    @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. – Servy Oct 26 '17 at 13:18
  • 3
    @StephenYork Since you require an implementation of `FeedCat`, `SellHouse` and `BuyCar` be provided to test the code, you can use this for each of them : `Task FeedCat(){return Task.Delay(1000);}Task SellHouse(){return Task.Delay(1000);}Task BuyCar(){return Task.Delay(1000);}`. You can use those methods to test with, and then just copy-paste the code from the answer into a method and run it to see it run in 1 second, rather than 3. – Servy Oct 26 '17 at 21:24
  • @Servy You see that actually helped a little. A previous comment you said to use `return Task.Delay...` which I figured was wrong because I could only do it with `await Task.Delay...`. Now I know you meant to NOT mark the method as async. Anyway, this conversation is a waste of energy from this point. Thanks for your time – Stephen York Oct 26 '17 at 21:42
  • 3
    @StephenYork I'm glad you were able to run the example and get the expected output with that information, and that you now understand that the `WhenAll` is simply unnecessary to solve this problem using your now working asynchronous methods. – Servy Oct 26 '17 at 21:45
  • @Servy You did clear up one point in the example, but there was still a large gaping hole as to the why. I've had a long discussion with a colleague who has actually provided a very clear reason as to "why" these run concurrently and yet still blocks on the last await statement. There's a larger picture around async / await when it comes to usage in an executable project vs hosted in IIS which has helped me. I admit that's beyond the scope of what this SO post is about but I still maintain that it would be helpful for you to explain in detail at the outset rather your approach in this thread. – Stephen York Oct 27 '17 at 00:03
  • 2
    @StephenYork There's nothing inherently different about how `await` works in an executable vs an IIS application. What difference do you think there is that's relevant? Of course this answer isn't designed to be a tutorial covering all of the basics of the TPL. That's not only beyond the scope of this specific question, it's also beyond the scope of what can really be provided in *any* given SO answer. That said, if you have a more specific question as to why or how this code works, then by all means, ask away, and I'll do my best to answer your question. – Servy Oct 27 '17 at 18:23
  • 1
    @Servy "Of course this answer isn't designed to be a tutorial covering all of the basics of the TPL" ...so why are you continuing the converstation them? Seeing you have there is a difference to do with if you're going to use async then it's necessary (more desirable) to make all calls in the chaing async otherwise long blocking calls can't be put to sleep by IIS and brought back when a response is finally received in order to free up the main thread, something with isn't an issue when running a vanilla desktop app. But functionally no, in usage it's identical. Anyway please stop replying. – Stephen York Oct 29 '17 at 01:22
  • Downvoted because: in your example the tasks run after eachother.HouseTask starts when CatTask is finished. By using WhenAll, all tasks run in parallel and all are done when you get the value using await. – Marc van Nieuwenhuijzen Jul 31 '18 at 13:34
  • 15
    @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. – Servy Aug 01 '18 at 13:58
  • 2
    @Servy Sorry. I did not look at the example really well. I missed that you started the tasks first and then await them. You are correct. I can not edit my downvote anymore. It is locked. But you are in fact correct. – Marc van Nieuwenhuijzen Aug 01 '18 at 14:16
  • 1
    I know this is an old post, however I must add my two cents. The two codes are not equivalent. If Task.WhenAll is removed, and an exception is thrown by any of the tasks, the following tasks will not be executed, whereas using WhenAll will execute all tasks and return the exceptions for the failed ones, and completion for the others. – gabnaim Nov 07 '22 at 20:17
  • 1
    @gabnaim It will start all of them in either case. This code will potentially return the exception before later tasks have finished, but they'll all run. The same exception will be in the resulting task in either case. In pretty much all situations not waiting when you already know the result is *desirable*, in fringe situations, it might not be. It's sort of like the difference between the short circuiting OR (||) and the non-short circuiting OR (}); in very fringe situations the latter might be important for your code, but usually it's a sign that you're over reliant on side effects. – Servy Nov 07 '22 at 20:25
  • Ok, it will start all of them. My point was that they are not equivalent as you seem to suggest. I needed to run all tasks to completion regardless of whether another failed, and this did not happen when I just awaited them separately. As these are long running tasks (file copy), if one fails early, pretty much all fail if I just await them all. This is why I Googled WhenAll in the first place, and it solved my problem. – gabnaim Nov 07 '22 at 20:33
  • 1
    @gabnaim That would only matter if you're ending the process on failure and need to leave time for those than won't fail to finish due to their side effects, which is a rather fringe reason that's *rarely* going to be needed. If you're using these async methods for their results, as this question is asking about, rather than their side effects, then waiting longer is just waiting longer, and isn't adding value. – Servy Nov 07 '22 at 21:08
  • I don't see how using proper exception handling for all your tasks is a "fringe reason". I would think that's the default case. I copy multiple files and exceptions are the standard in case of a network error or a file being in use. Using your solution, if one of them throws an exception, they all fail. What I need is to retry on the failing tasks and let the others finish. When I use Task.WhenAll, this works properly. I can send you my code to demonstrate this, but you can test it yourself. Please refer to @samfromlv's detailed answer explaining this. – gabnaim Nov 11 '22 at 17:03
  • @gabnaim The difference between your case, and the case in the question, is that this question is asking about performing async operations *because they need their results* and they're going to do something with them later. You're performing async operations *for their side effects* and don't have any results for them. They're different cases. When the async operations are run to compute a result *this is the desirable exception handling behavior*. – Servy Nov 11 '22 at 17:24
  • @gabnaim If you're writing async code in which, by default, your operations perform side effects, I'd suggest re-evaluating how you program. Sometimes it's unavoidable (an async operation to create a file can't avoid it, obviously). But that aside, you seem to think I'm saying one should never use `Whenall`, which just isn't true. It's a helpful method that I use regularly. It's just not (typically) appropriate *in the specific situation asked about*. – Servy Nov 11 '22 at 17:28
  • Samfromlv has elaborated on why your solution does not handle exceptions properly. A failing task interrupting the others will happen no matter if the results are used or not. The fact is that your solution will work in some cases and not in others, whereas Taks.WhenAll will simply just work. This site is visited by programmers of all levels and async programming is difficult even for seasoned programmers. If your example only works in some cases, you may want to update you answer with a summary of the discussions here. One should not have to read them to understand those differences. – gabnaim Nov 11 '22 at 19:49
  • @gabnaim It does handle exceptions properly. Given the situation at hand, *this is the proper way to handle exceptions*. Given some different situation not asked about, exceptions may need to be handled differently. One of the tasks faulting doesn't interrupt any others in this code. It merely faults the composite task. That doesn't interrupt anything, by its nature. That you did that in your program is because *you wrote your program to do that*. This answer isn't here to tell everyone how to write every asynchronous program they could ever write, it's here to answer *this one question*. – Servy Nov 11 '22 at 20:29
  • 1
    That you have an entirely different problem that isn't related to this question, and that you want to use a different solution than what is appropriate for the problem in this situation *is irrelevant to this question or its answers*.. You're free to ask about the problem you're having if you're struggling to solve it. – Servy Nov 11 '22 at 20:29
  • 1
    Actually, my problem is that you are not being helpful. Notice that yours is not the accepted answer. Notice that many ask for clarifications. You write long answers in the notes, but your original should reflect the feedback and offer clarification for those questions asked. If it does not do so, it is not HELPFUL. You may be absolutely right, but the point here is to help others, not to be right. I think I have said everything to be said here. Have a great day! – gabnaim Nov 15 '22 at 16:13
  • Btw I heavily disagree that fire-and-forgetting tasks is generally the desirable behavior. A common reaction to a failed operation is to retry the operation. In this case the operation is all three tasks combined. In case the combined operation fails, the user might start a new combined operation. If you allow fire-and-forget, a new `SellHouse` operation might get started while a fire-and-forgotten `SellHouse` still runs in the background unobserved. Except from selling inadvertently your house twice, you also risk parallelizing an operation not designed for concurrency. – Theodor Zoulias Nov 26 '22 at 01:31
  • Hi @Servy, as somebody learning about TPL, your answer helped but only because I read the many (sometimes contradictory) comments under both yours and the top voted answer. I humbly wish you would consider adding simple clarification to your answer, which I believe should be the accepted answer. It isn’t immediately obvious that the tasks run before await is called on them, at least not to a significant number of people. You obviously want to help people, so I ask humbly for you to consider adding clarification. – Wayne Uroda Feb 09 '23 at 00:19
  • Shouldn't this answer be the correct one for this specific scenario? – ECDev01 Mar 30 '23 at 02:55
56

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.

Joel Mueller
  • 28,324
  • 9
  • 63
  • 88
  • Tuples are the only C# 7 feature involved here. Those are definitely in the final release. – Joel Mueller Apr 08 '17 at 23:23
  • I know about tuples and c# 7. I mean I can't find the method WhenAll which return tuples. What namespace/package? – Yury Scherbakov Apr 10 '17 at 10:10
  • 1
    @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. – Chris Charabaruk Aug 18 '17 at 23:20
  • 2
    I'd suggest replacing the `.Result` calls as per Stephen's reasoning to avoid other people perpetuating the bad practice by copying your example. – julealgon Oct 02 '18 at 20:28
  • 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? – Ian Grainger Sep 16 '19 at 07:52
  • 1
    @nrofis That's false. Both tasks are created and therefore started before either is awaited. It's equivalent to [Servy's answer](https://stackoverflow.com/a/17197745/5405967). – MarredCheese Sep 19 '21 at 21:09
  • Sorry, you are right – nrofis Sep 20 '21 at 22:23
  • 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. – samfromlv Mar 09 '23 at 13:04
32

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 discreet 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>

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • "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 Feb 06 '18 at 16:24
  • @Servy you're right, that was comments; I'll add a tweak to show using the results – Marc Gravell Feb 06 '18 at 16:28
  • @Servy tweak added – Marc Gravell Feb 06 '18 at 16:33
  • Also if you're going to take the early out of handing synchronous tasks, you might as well handle any tasks synchronously being cancelled or faulted, rather than just those completed successfully. If you've made the decision that it's an optimization that your program needs (which will be rare, but will happen) then you might as well go all the way. – Servy Feb 06 '18 at 16:34
  • 1
    @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 – Marc Gravell Feb 06 '18 at 16:37
  • Indeed you wouldn't want to call `Result` to let it throw exceptions as it doesn't have desirable semantics. You'd need to use `Task.FromException` to construct the proper result. It would be more work (although work that you could just write once and stick in a method, so not *that* much more work) but I assume anyone who can't afford to take the time to create one object if the tasks are all completed wants to avoid it even when some are faulted or cancelled. – Servy Feb 06 '18 at 16:57
  • Does this mean that `await Task.Delay(#)` runs synchronously? The thing is that this code block does execute sequentially: `DoNothing(await Wait0().ConfigureAwait(configureAwait), await Wait0().ConfigureAwait(configureAwait));` Where `async Task Wait0() { await Task.Delay(500); return 0; }`. In my tests it makes no difference on .Net Core 3.1 if `configureAwait=true` or `configureAwait=false`. – Almis Feb 17 '20 at 08:40
  • 1
    @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": https://gist.github.com/mgravell/8e08a7d3dbf3bbfc17ff46c494a59150 – Marc Gravell Feb 17 '20 at 10:52
  • 4
    @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. – Almis Feb 19 '20 at 14:58
14

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

samfromlv
  • 1,001
  • 1
  • 10
  • 17
  • 2
    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. – gabnaim Nov 07 '22 at 20:23
  • 1
    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? – Florent Mar 08 '23 at 12:27
  • 1
    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 - https://stackoverflow.com/a/24657079/463640 – samfromlv Mar 09 '23 at 12:55
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;
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 2
    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 ?? – Kraang Prime Jul 30 '16 at 23:09
  • 2
    @sanuel if they return task , then no... they start the async open, but don't wait for it – Reed Copsey Jul 30 '16 at 23:25
  • 1
    I don't think this is accurate, please see the discussions under @StephenCleary's answer... also see Servy's answer. – Rosdi Kasim Jan 10 '17 at 04:37
  • 2
    if I need to add .ConfigrtueAwait(false). Would I add it to just Task.WhenAll or to each awaiter that follows? – AstroSharp Feb 24 '17 at 18:19
  • 1
    @AstroSharp in general, it's a good idea to add it to all of them (if the first is completed, it gets effectively ignored), but in this case, it'd probably be okay to just do the first - unless there's more async stuff happening later. – Reed Copsey Feb 24 '17 at 19:05
  • 1
    @KraangPrime No, it's not useless. If the task has already completed then yes it won't make any difference, but if these are long running tasks, then this approach means that each task runs concurrently and nothing continues past then whenall until every task has completed...granted though a task MAY finish immediately – Stephen York Oct 25 '17 at 21:27
  • 1
    According to comments to https://stackoverflow.com/a/17197745/2305386 the `await Task.WhenAll` is not needed here, the three tasks started together and after that awaited together will run in parallel. – user44 Jun 19 '18 at 08:14
  • 2
    @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). – Reed Copsey Jun 19 '18 at 18:23
4

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

Community
  • 1
  • 1
Christian Phillips
  • 18,399
  • 8
  • 53
  • 82
3

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);
     }
 }
XDS
  • 3,786
  • 2
  • 36
  • 56
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.
It'sNotALie.
  • 22,289
  • 12
  • 68
  • 103
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.

    try { await Task.WhenAll(task1, task2, task3).ConfigureAwait(false); } catch { }

    // 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.");

The try block with the empty catch is required, because the .NET 7 still doesn't offer a proper way to await a task without throwing in case of cancellation or failure.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
-1
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.

  • 8
    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. – Joel Mueller Nov 09 '19 at 22:34
-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!
Mathias Nohall
  • 737
  • 1
  • 9
  • 9