【问题标题】:Start async operations, then await later启动异步操作,稍后等待
【发布时间】:2013-09-22 10:19:39
【问题描述】:

我现在才开始使用 async 和 await。我有 8 个单独的数据库调用,其中一些依赖于其他调用。我希望能够启动 3 个异步方法,然后当某个 1 返回时,启动 3 个其他方法,然后当某个 1 从该方法返回时,再启动 2 个。我目前正在使用 3 Parallel.Invoke 方法来完成此操作,但每个并行都必须等到 ALL methods 返回。我只关心 1 方法 返回,其他方法可以在后台运行,直到最后出现 await Task.WhenAll(t1,t2,t3,...,t6)有没有办法通过 async/await 来解决这个问题?

我知道 await 不会阻塞,但它会停止执行我的 main 方法(带有 8 个 db 调用),直到值从方法返回(就像同步方法一样)。

【问题讨论】:

    标签: c# asynchronous async-await


    【解决方案1】:

    您可以使用Task.WhenAny 等待多个任务中的任何一个:

    var completedTask = await Task.WhenAny(t1, t2, t3, ...);
    

    如果你有一个更复杂的依赖结构,那么我建议使用async 方法来表示它:

    static async Task DoMasterOperationAsync()
    {
      var result = await StartSomething();
      await Task.WhenAll(DoComplexOperationAsync(), result.NextT1Async());
    }
    
    static async Task DoComplexOperationAsync()
    {
      var result1 = await T1Async();
      await Task.WhenAll(result1.NextT1Async(), result1.NextT2Async(), result1.NextT3Async());
    }
    
    await Task.WhenAll(DoMasterOperationAsync(), t2, t3, ...);
    

    【讨论】:

    • 我一直在寻找的功能可以通过这种方式实现。在您发表评论后,我在MSDN documentation 中读到 Task.Run 专门用于计算绑定任务。我将此标记为答案,但您能告诉我为什么这比 Task.Run 更好吗?假设我不在乎我在调用期间用完了线程,这有关系吗?这个解决方案迫使我创建中间 POCO 来移动数据,而且我不太清楚。
    • Task.Run 将创建一个必须在线程池上执行的新 Task。在这种情况下,它只是有更多的开销。与额外的线程池任务相比,额外的 POCO 实例成本更低。
    • 另外,您可以创建一个var parallelOperations = new List<Task>();,然后添加您想要稍后等待的任务,例如parallelOperations.Add(context.InsertAsync(pocoObj)); 最后是 if (parallelOperations.Any()) { await Task.WhenAll(parallelOperations); } - 如果您有很多任务要等待,这尤其有意义。
    【解决方案2】:

    我的问题是我不知道Task.Run 可以让你开始一个任务,然后等待它。我在@StephenCleary 的another answer 中找到了这个。谢谢斯蒂芬,你在那里得到了我的支持!这是我完全人为的例子,它完成了我上面要做的事情:

            Func<Task<int>> t1 = async () => { await Task.Delay(4000); return 1; };
            Func<Task<int>> t2 = async () => { await Task.Delay(5000); return 2; };
            Func<int, Task<int>> t3 = async (val) => { await Task.Delay(3000); return 3; };
            Func<int, Task<int>> t4 = async (val) => { await Task.Delay(4000); return 4; };
            Func<int, Task<int>> t5 = async (val) => { await Task.Delay(3000); return 5; };
            Func<int, Task<int>> t6 = async (val) => { await Task.Delay(2000); return 6; };
            Func<int, Task<int>> tSecondary = async (val) => { await Task.Delay(3000); return 7; };
            Func<Task<int>> tPrimary = async () => { await Task.Delay(2000); return 8; };
            //kick off first 3 calls
            var primaryR = Task.Run(tPrimary);
            var t1R = Task.Run(t1);
            var t2R = Task.Run(t2);
            //await 1 of the 3
            var primary = await primaryR;
            //start 2 of 3 dependent tasks
            var t3R = Task.Run(() => t3(primary));
            var t4R = Task.Run(() => t4(primary));
            //kick off and await secondaryMasterTaskAsync
            var secondary = await tSecondary(primary);
            //start final 2
            var t5R = Task.Run(() => t5(secondary));
            var t6R = Task.Run(() => t6(secondary));
            //await all tasks that haven't been awaited previously
            var tasks = await Task.WhenAll(t1R, t2R, t3R, t4R, t5R, t6R);
    

    【讨论】:

    • 只有在有 CPU 密集型工作要做时才应该使用 Task.Run 来释放 UI 线程。在这种情况下,由于您的操作都是 I/O 绑定的(数据库调用),因此您不应使用 Task.Run。相反,按照我在回答中的描述,将调用逻辑拆分为 async 方法。
    猜你喜欢
    • 1970-01-01
    • 2019-10-08
    • 1970-01-01
    • 2016-12-26
    • 1970-01-01
    • 2016-03-06
    • 2018-06-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多