开始异步操作,稍后再等待。

10
我现在刚开始使用async和await。我有8个不同的数据库调用,一些是依赖于其他的。我想要启动3个异步方法,当某一个方法返回时,再启动另外3个异步方法,然后再当某一个方法返回时,再启动2个异步方法。我目前正在使用3个Parallel.Invoke方法来实现这个,但是每个并行必须等待所有方法返回。我只关心1个方法返回,其余可以在后台运行,直到最后使用await Task.WhenAll(t1,t2,t3,...,t6)有没有一种方式可以使用async/await来实现呢? 我知道await不会阻塞,但它会停止我的主方法(具有8个数据库调用)的执行,直到从方法中返回值(就像同步方法一样)。
2个回答

5
您可以使用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文档中阅读到Task.Run专门用于计算绑定任务。我将其标记为答案,但您能告诉我为什么这比Task.Run更好吗?假设我不关心调用期间使用线程的数量,这有关系吗?这个解决方案强制我创建中间POCO以移动数据,并且对我来说不太清晰。 - DougJones
Task.Run 将创建一个新的 Task,必须在线程池上执行。在这种情况下,它只是有更多的开销。与额外的线程池任务相比,额外的 POCO 实例是便宜的。 - Stephen Cleary
此外,您可以创建一个 var parallelOperations = new List<Task>();,然后添加您想要稍后等待的任务,例如 parallelOperations.Add(context.InsertAsync(pocoObj));,最后执行 if (parallelOperations.Any()) { await Task.WhenAll(parallelOperations); } - 如果您有超过一手指数量的任务需要等待,这将特别有意义。 - Matt

0
我的问题是我不知道有一个名为Task.Run的方法,它可以让你启动一个任务,然后稍后再等待它。我在另一个答案(作者@StephenCleary)中发现了这一点。谢谢Stephen,你得到了我的赞成!以下是一个完全刻意的示例,它实现了我上面所说的目标:
        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);

2
当您需要执行CPU密集型操作时,应仅使用Task.Run来释放UI线程。在这种情况下,由于您的操作全部都是I/O绑定(db调用),因此不应使用Task.Run。相反,根据我的回答,在逻辑上将调用拆分为“async”方法。 - Stephen Cleary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接