如何在另一个异步方法内调用异步方法而不陷入阻塞?

4

我需要调用多个异步方法,在它们之内,还需要调用另一个异步方法。让我演示一下:

private async void button1_Click(object sender, EventArgs e)
{
    for(int i = 0; i< 100; i++)
    {
        await Method1();
    }
}

public async Task Method1()
{
    await Task.Delay(3*1000);
    await Method2();
}

public async Task Method2()
{
    await Task.Delay(10*1000);
}

我的问题是,for语句只有在等待Method2开始后才会激活迭代,而我想一次性创建100个任务。其他所有操作都将异步完成。

2个回答

8
我觉得你对"await"的含义有些困惑。"await"的意思是“以异步的方式开始处理这件事情,而当它在后台运行时,返回到Windows消息循环并继续处理消息,以便UI保持重新绘制状态。当异步任务完成时,从我等待的地方恢复代码。” 所以,当你在循环中使用await时,你的意思是:
  • 启动第一个异步作业...
  • 在它运行的同时,继续处理消息循环。
  • 当第一个异步作业完成时...
  • 从我们等待的地方继续循环。我们绕过循环然后...
  • 启动第二个异步作业...
如果这不是您想要的结果,请不要等待该任务。如果您想要同时启动一百个任务,则启动一百个任务并不等待任何一个任务。为什么单击处理程序中会有任何等待呢?

异常处理怎么办?如果你不使用await等待Task的执行(或以其他方式处理其结果),那么在异步方法执行期间发生的任何异常都将被静默忽略。 - svick

3

与其在for循环中等待每个任务完成,您只需要启动所有任务。您不希望延迟下一个任务的调度直到前一个完成,因此不要使用await

private void button1_Click(object sender, EventArgs e)
{
    for(int i = 0; i< 100; i++)
    {
        var task = Method1();
    }
}

完成。

如果在所有任务都完成后,您需要执行某些操作,同时仍然并行执行所有任务,则可以依靠 Task.WhenAll 生成一个任务,该任务将在所有其他任务完成时完成:

private async void button1_Click(object sender, EventArgs e)
{
    var tasks = new List<Task>();
    for(int i = 0; i< 100; i++)
    {
        tasks.Add(Method1());
    }
    await Task.WhenAll(tasks);
    textbox1.Text = "Done!"; //or whatever you want to do when they're all done
}

我认为你几乎总是应该await这个task。对于你的第一个片段,如果在Method1()中有未处理的异常,它将会悄无声息地消失,我认为这是非常糟糕的事情。 - svick
@svick 处理异常有很多方法。如果不知道抛出异常时你计划做什么,很难建议解决方案。您可以添加一个“onerror”继续运行,也可以确保在Method1中处理错误而不是从调用者处处理(如果需要,将其包装在另一个方法中来处理异常)。 - Servy
@jure 为什么你想这样做呢?该方法是异步的,因此并行启动它不太可能提高性能。 - svick
@Svick 我在并行编程方面经验不是很丰富。从来没有需要使用大量的并行处理,所以这更多地是我的问题。像Servy建议的那样在一个for循环中启动100个任务,和使用Parallel.For启动它是一样的吗? - Jurica Smircic
@jure 这并不完全相同,行为会有所不同。但是启动一个 async 方法(这就是调用它的作用)通常不需要很长时间,因此并不值得将其并行化。 - svick
显示剩余3条评论

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