ChatGPT解决这个技术问题 Extra ChatGPT

Awaiting multiple Tasks with different results

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?

Do you have any ordering requirement? That is, do you want to not sell the house until after the cat is fed?

S
Servy

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;

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.


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.
S
Servy

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;

@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.
J
Joel Mueller

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.


Tuples are the only C# 7 feature involved here. Those are definitely in the final release.
I know about tuples and c# 7. I mean I can't find the method WhenAll which return tuples. What namespace/package?
@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.
@nrofis That's false. Both tasks are created and therefore started before either is awaited. It's equivalent to Servy's answer.
W
Wai Ha Lee

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:

with recent C#, a common pattern is for the async fallback method is commonly implemented as a local function: Task DoTheThings() { async Task Awaited(Task a, Task b, Task c) { return DoWhatever(await a, await b, await c); } Task x = FeedCat(); Task y = SellHouse(); Task 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); } prefer ValueTask to Task if there is a good chance of things ever completely synchronously with many different return values: ValueTask DoTheThings() { async ValueTask Awaited(ValueTask a, Task b, Task c) { return DoWhatever(await a, await b, await c); } ValueTask x = FeedCat(); ValueTask y = SellHouse(); ValueTask z = BuyCar(); if(x.IsCompletedSuccessfully && y.IsCompletedSuccessfully && z.IsCompletedSuccessfully) return new ValueTask( 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); } if possible, prefer IsCompletedSuccessfully to Status == TaskStatus.RanToCompletion; this now exists in .NET Core for Task, and everywhere for ValueTask


"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 tweak added
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.
@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.
R
Reed Copsey

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;

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 , 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).
s
samfromlv

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:

SellHouse is already completed successfully when FeedCat failed. In this case you are fine. SellHouse is not complete and fails with exception at some point. Exception is not observed and will be rethrown on finalizer thread. 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


C
Community

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


X
XDS

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

I
It'sNotALie.

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.

mm...not Task.Value (maybe it used to exist in 2013?), rather tCat.Result, tHouse.Result or tCar.Result
t
taurius litvinavicius
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.


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.
M
Mathias Nohall

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!

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