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

C# 等待具有不同结果的多个任务,c#,.net,async-await,task-parallel-library,.net-4.5,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() {} 专用异步任务FeedCat(){} 私有异步任务SellHouse(){} 私有异步任务BuyCar(){} 它们都需要运行,然后我的代码才能继续,我还需要每个代码的结果。所有结果都没有任何共同之处 如何调用并等

我有3项任务:

private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
专用异步任务FeedCat(){}
私有异步任务SellHouse(){}
私有异步任务BuyCar(){}
它们都需要运行,然后我的代码才能继续,我还需要每个代码的结果。所有结果都没有任何共同之处


如何调用并等待这三项任务完成,然后获得结果?

在启动所有任务后,只需分别等待这三项任务即可

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

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

使用
whalll
后,可以使用
wait
单独拉出结果:

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
在其他情况下可能会导致问题。

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

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;

您可以使用前面提到的
Task.whalll
,或者
Task.WaitAll
,具体取决于您是否希望线程等待。请查看链接以了解两者的解释


使用
任务。当所有任务完成后,等待结果:

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.
如果您使用的是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);
    }
}
但是,如果您想将此示例变成现实,请参阅Marc Gravell的答案,了解有关ValueTask和已完成任务的一些优化。

向前警告

对于那些访问此线程和其他类似线程,寻找使用async+Wait+task工具集并行化EntityFramework的方法的人来说,这只是一个简短的开场白。:这里显示的模式是合理的,但是,当涉及到EF的特殊雪花时,除非使用单独的(新)线程,否则无法实现并行执行涉及的每个*Async()调用中的db上下文实例

由于ef db上下文固有的设计限制,禁止在同一ef db上下文实例中并行运行多个查询,因此这种事情是必要的


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

  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);
   }
 }
公共异步任务Foobar(){
等待异步任务(任务a、任务b、任务c){
返回剂量测量(等待a、等待b、等待c);
}
使用(var carTask=BuyCarAsync())
使用(var catTask=FeedCatAsync())
使用(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));//快速通道
}
cat=等待任务;
car=等待carTask;
house=等待house任务;
//或任务。等待全部(carTask、catTask、houseTask);
//或等待任务。WhenAll(carTask、catTask、houseTask);
//这取决于您如何更好地处理异常
等待返回(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);
     }
 }
公共异步任务Foobar(){
使用(var carTask=BuyCarAsync())
使用(var catTask=FeedCatAsync())
使用(var houseTask=SellHouseAsync())
{
cat=catTask.Status==TaskStatus.RanToCompletion?catTask.Result:(等待catTask);
car=carTask.Status==TaskStatus.RanToCompletion?carTask.Result:(等待carTask);
house=houseTask.Status==TaskStatus.RanToCompletion?houseTask.Result:(等待houseTask);
返回剂量(猫、车、房子);
}
}

给定三个任务-
FeedCat()
SellHouse()
BuyCar()
,有两个有趣的情况:要么它们都是同步完成的(出于某种原因,可能是缓存或错误),要么不是

假设我们有,从这个问题:

Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();
    // what here?
}
但是。。。这不便于处理结果;我们通常希望
等待

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);
}
与上面的一些注释相反,使用
wait
而不是
Task。whalll
对任务的运行方式(并发、顺序等)没有影响。在最高级别上,
Task.whalll
早于
async
/
wait
的良好编译器支持,并且在这些东西不存在时非常有用。当您有一个任意的任务数组,而不是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);
}
  • 如果有很好的机会可以使用许多不同的返回值完全同步,则宁愿使用
    ValueTask
    而不是
    Task

    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);
    }
    
    ValueTask DoTheThings(){
    等待异步ValueTask(ValueTask a、Task b、Task c){
    返回DoWhatever(等待a、等待b、等待c);
    }
    ValueTask x=FeedCat();
    ValueTask y=SellHouse();
    ValueTask z=BuyCar();
    如果(x.IsCompletedSuccessfully&&
    y、 我成功地完成了&&
    z、 (成功完成)
    返回新值任务(
    DoWhatever(a.Result,b.Result,c.Result));
    //我们可以安全访问。结果,正如他们所知
    //完成
    等待返回(x,y,z);
    }
    
  • 如果可能,宁愿
    IsCompletedSuccessfully
    而不是
    Status==TaskStatus.RanToCompletion
    ;现在,它存在于.NET Core中,用于
    任务
    ,也存在于
    值任务


  • 如果您试图记录所有错误,请确保
    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);
    }
    
    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);
    }
    
    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);
    }
    
    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);
    }
    
    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;
    
    var catTask = FeedCat();
    var houseTask = SellHouse();
    var carTask = BuyCar();
    
    var cat = await catTask;
    var house = await houseTask;
    var car = await carTask;
    
    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()<---
    
    var dn = await Task.WhenAll<dynamic>(FeedCat(),SellHouse(),BuyCar());
    
    var ct = (Cat)dn[0];
    
    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!