等待期间多个任务的控制流

本文关键字:任务 控制流 等待 | 更新日期: 2023-09-27 18:24:57

当我有两个任务而等待一个任务时,我很难理解等待期间的控制流。异步程序(C#和Visual Basic)中的控制流只使用了一个等待,所以它没有帮助。

我有以下代码:

public async Task Loop(string name, int count)
{
    for (int i = 0; i < count; i++)
    {
        Console.WriteLine("{0}: {1} Before", name, i);
        await Task.Delay(10);
        Console.WriteLine("{0}: {1} After", name, i);
    }
    Console.WriteLine("{0} COMPLETE", name);
}
[TestMethod]
public async Task TwoLoopsWithSeparateAwait()
{
    var loopOne = Loop("ONE", 3);
    Console.WriteLine("After ONE Creation");
    var loopTwo = Loop("TWO", 3);
    Console.WriteLine("After TWO Creation, before ONE await");
    await loopOne;
    Console.WriteLine("After ONE await");
    await loopTwo;
    Console.WriteLine("After TWO await");
}

结果如下:

  • ONE:0之前
  • 创造ONE之后
  • TWO:0之前
  • 二次创造之后,一次等待之前
  • 二:0之后
  • 二:1之前
  • 一:0之后
  • ONE:1之前
  • 一:1之后
  • ONE:2之前
  • 二:1之后
  • 二:2之前
  • 二:2之后
  • 两个完整
  • 一:2之后
  • 一个完整
  • 在ONE等待之后
  • 两个等待之后

根据等待(C#参考):

等待表达式不会阻塞正在执行它的线程。相反,它会导致编译器将异步方法的其余部分注册为等待任务的延续。控件然后返回到异步方法的调用方。当任务完成时,它调用它的continuation,异步方法的执行从它停止的地方恢复

我理解等待loopOne之前会发生什么,但我认为只有loopOne会被执行(可能是同步的),它看起来更像是一个yield,它保存了可能继续的任务列表(我认为这符合"当任务完成时,它调用其继续")。

在等待多个任务期间,控制流背后的逻辑是什么?

等待期间多个任务的控制流

async方法被同步执行,直到它到达await。然后,它创建一个任务,表示您正在等待的async操作以及代码的其余部分,作为操作完成时要执行的延续。这意味着在async方法中,当到达第一个await时,控制实际上返回给具有热任务的调用者。当await到达该延续内时,它再次注册延续,依此类推。

因此,在您的情况下,执行顺序应该是:

  • 第一个循环的同步部分
  • 第二个循环的同步部分
  • 第一个循环的延续
  • 第二个循环的延续
  • 调用方在第一个循环之后的延续
  • 调用方在第二个循环之后的继续

中间的延续可以很容易地按照不同的顺序进行,这取决于异步操作实际花费的时间。

你可以想象这里有三种不同的流动。第一个调用执行两个Loop调用的同步部分,启动两个并发的async操作和continuation流,然后向第一个调用注册continuation,该调用将向第二个调用注册continuation。