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

问题描述 投票:200回答:10

我有3个任务:

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

它们都需要先运行,然后我的代码才能继续,而且我也需要它们的结果。没有结果有任何共同点

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

c# .net async-await task-parallel-library .net-4.5
10个回答
339
投票

使用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可能会在其他情况下引起问题。


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

81
投票

在全部启动后,只需将三个任务分开await

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

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

32
投票

如果您使用的是C#7,则可以使用这种方便的包装方法...

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

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

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

但是,如果您打算将本示例转变为真实的示例,请参阅Marc Gravell的答案,围绕ValueTask和已完成的任务进行一些优化。


11
投票

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

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;

11
投票

给出三个任务-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 predatesasync / await具有良好的编译器支持,并且在当不存在这些内容时有用。当您有任意多个任务而不是3个谨慎的任务时,它也很有用。

但是:我们仍然有一个问题,async / await会为继续产生大量编译器噪声。如果任务might实际上实际上是同步完成的,那么我们可以通过构建具有异步回退的同步路径来优化此任务:

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. 如果可能,将IsCompletedSuccessfully优先于Status == TaskStatus.RanToCompletion;现在,它存在于.NET Core中的Task中,并且在ValueTask<T>的任何地方都存在]]


3
投票

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


2
投票

您可以使用Task.WhenAllTask.WaitAll,具体取决于您是否希望线程等待。请查看链接以了解这两种情况。


2
投票

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


0
投票

前警告

© www.soinside.com 2019 - 2024. All rights reserved.