等待具有不同结果的多个任务

时间:2013-06-19 17:36:52

标签: c# .net async-await task-parallel-library .net-4.5

我有3个任务:

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

他们都需要在我的代码继续之前运行,我也需要每个代码的结果。没有任何结果彼此有任何共同之处

如何调用并等待3个任务完成然后获得结果?

11 个答案:

答案 0 :(得分:302)

使用WhenAll后,您可以使用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;

你也可以使用Task.Result(因为你知道他们已经成功完成了)。但是,我建议使用await,因为它显然是正确的,而Result可能会在其他情况下导致问题。

答案 1 :(得分:72)

启动所有这三项任务后,只需await这三项任务。

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

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

答案 2 :(得分:27)

如果你正在使用C#7,你可以使用像这样的方便的包装方法......

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

...当您想要等待具有不同返回类型的多个任务时,启用这样的方便语法。当然,您必须为不同数量的任务进行多次重载。

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

答案 3 :(得分:10)

您可以将它们存储在任务中,然后等待它们:

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;

答案 4 :(得分:8)

鉴于三项任务 - FeedCat()SellHouse()BuyCar(),有两个有趣的案例:它们都是同步完成的(出于某种原因,可能是缓存或错误),或者它们是不。

让我们说,问题是:

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

现在,一个简单的方法是:

Task.WhenAll(x, y, z);

但......对于处理结果不方便;我们通常希望await

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

但这会产生大量开销并分配各种数组(包括params Task[]数组)和列表(内部)。它有效,但它不是伟大的IMO。在很多方面,使用async操作更简单依次只有await

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

与上面的一些评论相反,使用await代替Task.WhenAll会使没有区别与任务的运行方式(并发,顺序等)。在最高级别,Task.WhenAll 早于async / await的良好编译器支持,并且当这些内容不存在时非常有用 。当你拥有任意数组的任务而不是3个谨慎的任务时,它也很有用。

但是:我们仍然遇到async / await为延续生成大量编译器噪音的问题。如果任务可能可能实际上是同步完成的,那么我们可以通过构建具有异步回退的同步路径来优化它:

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

这种“具有异步回退的同步路径”方​​法越来越普遍,尤其是在同步完成相对频繁的高性能代码中。请注意,如果完成始终是真正的异步,它将毫无帮助。

此处适用的其他内容:

  1. 使用最近的C#,async回退方法的常见模式通常是作为本地函数实现的:

    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. 如果很有可能与许多不同的返回值完全同步,则会更喜欢ValueTask<T>Task<T>

    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. 如果可能,请选择IsCompletedSuccessfullyStatus == TaskStatus.RanToCompletion;这个现在存在于Task的.NET Core中,以及ValueTask<T>

  4. 的所有位置

答案 5 :(得分:3)

如果您尝试记录所有错误,请确保在代码中保留Task.WhenAll行,很多注释表明您可以删除它并等待单个任务。 Task.WhenAll对于错误处理非常重要。如果没有这一行,您可能会将代码保留为未观察到的异常。

didreceivepushnotification

想象一下,FeedCat会在以下代码中抛出异常:

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;

在这种情况下,你永远不会等待houseTask或carTask。这里有3种可能的情况:

  1. 当FeedCat失败时,SellHouse已成功完成。在 这种情况你很好。

  2. SellHouse不完整,但在某些时候失败。没有观察到异常,将在终结器线程上重新抛出。

  3. SellHouse不完整,包含等待内容。如果 你的代码在ASP.NET中运行SellHouse会尽快失败 等待将在里面完成。这是因为你基本上 制造火灾&amp;忘记呼叫和同步上下文在FeedCat失败后立即丢失。

  4. 以下是案例(3)的错误:

    var catTask = FeedCat();
    var houseTask = SellHouse();
    var carTask = BuyCar();
    
    var cat = await catTask;
    var house = await houseTask;
    var car = await carTask;
    

    对于情况(2),您将得到类似的错误,但具有原始异常堆栈跟踪。

    对于.NET 4.0及更高版本,您可以使用TaskScheduler.UnobservedTaskException捕获未观察到的异常。对于.NET 4.5及更高版本,对于.NET 4.0,默认情况下会吞下未观察到的异常,未观察到的异常会导致进程崩溃。

    此处有更多详情:Task Exception Handling in .NET 4.5

答案 6 :(得分:2)

您可以使用上述Task.WhenAllTask.WaitAll,具体取决于您是否希望线程等待。请查看链接以获取对两者的解释。

WaitAll vs WhenAll

答案 7 :(得分:2)

使用Task.WhenAll然后等待结果:

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.

答案 8 :(得分:1)

var dn = await Task.WhenAll<dynamic>(FeedCat(),SellHouse(),BuyCar());

如果要访问Cat,请执行以下操作:

var ct = (Cat)dn[0];

这是非常简单的操作,并且非常有用,不需要复杂的解决方案。

答案 9 :(得分:0)

前瞻警告

对于那些正在寻找使用async + await +任务工具集并行化EntityFramework的方法的访问者和其他类似线程的快速准备:此处显示的模式是合理的,但是,当它来到EF的特殊雪花你不会实现并行执行,除非你在每个* Async()调用中使用一个单独的(新的)db-context-instance。

由于ef-db-contexts的固有设计限制,这种事情是必要的,它禁止在同一个ef-db-context实例中并行运行多个查询。

利用已经给出的答案,即使在一个或多个任务导致异常的情况下,这也是确保收集所有值的方法:

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

具有或多或少相同性能特征的替代实现可以是:

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

答案 10 :(得分:-1)

是不是 await 语句使代码按顺序运行?考虑下面的代码

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!