65.9K
CodeProject 正在变化。 阅读更多。
Home

await 关键字工作原理详解

starIconstarIconstarIconstarIconstarIcon

5.00/5 (30投票s)

2022 年 3 月 15 日

CPOL

18分钟阅读

viewsIcon

17315

downloadIcon

191

await 关键字的内部工作原理

引言

本文将深入探讨 await 关键字的工作原理。我们将通过分析 C# 编译器在编译以下五行代码程序时生成的代码来做到这一点。

public static async Task Main()
{
    DoWork();                        // ┐ Part 1
    await Task.Delay(2000);          // ┘

    MoreWork();                      // ┐ Part 2
    Console.Write("Press Enter: ");  // │
    Console.ReadLine();              // ┘
}

该程序通过调用 DoWork() 执行一些工作,然后执行 await,最后通过调用 MoreWork() 执行更多工作。程序最后等待用户按下 Enter 键。

我们将分析 C# 编译器编译此程序时生成的代码,然后将编译后的代码翻译回 C#。

目录

  1. 完整的 C# 程序反编译
  2. ildasm — C# 反编译器
  3. 类 Program
    1. 构造函数
    2. <Main> : void()
    3. Main : class [mscorlib]System.Threading.Tasks.Task() → SubMain()
    4. Display()
    5. DoWork()
    6. MoreWork()
    7. 类 StateMachine
      1. 构造函数
      2. SetStateMachine()
      3. MoveNext()
  4. 编译后程序的忠实 C# 表示
  5. 简化
    1. 简化 Main()
    2. 简化 MoveNext)()
    3. 简化 SubMain()
  6. 简化的 C# 表示
  7. 追踪 await 期间发生的情况
    1. 第一部分
    2. 第二部分
  8. 在 Button_Click 事件处理程序中使用 await
    1. 追踪 AwaitUnsafeOnCompleted
  9. 计时器
  10. 摘要
  11. 相关文章
  12. 历史

完整的 C# 程序反编译

这是我们将要测试的程序的完整 C# 代码。

namespace ConsoleAppTCSa
{
    class Program
    {
        public static async Task Main()
        {
            DoWork();                        // ┐ Part 1
            await Task.Delay(2000);          // ┘

            MoreWork();                      // ┐ Part 2
            Console.Write("Press Enter: ");  // │
            Console.ReadLine();              // ┘
        }

        private static void DoWork()
        {
            Display("Enter DoWork()");
            Thread.Sleep(2000);
            Display("Exit DoWork()");
        }

        private static void MoreWork()
        {
            Display("Enter MoreWork()");
            Thread.Sleep(2000);
            Display("Exit MoreWork()");
        }

        private static void Display(string s)
        {
            Console.WriteLine("{0:hh\\:mm\\:ss\\:fff} {1}", DateTime.Now, s);
        }
    }
}

我们将把这个 C# 程序编译成一个 *.exe 文件,然后反编译这个 *.exe 文件,以查看 C# 编译器生成的 CIL 代码。(对于使用 .NET Core 而不是 .NET Framework 编译的程序,请反编译 *.dll 文件。)

CIL 是“通用中间语言”,有时也称为 MSIL – Microsoft 中间语言,或简称为 IL – 中间语言。这是 C# 编译器输出的类似汇编的语言。如果你不熟悉这种语言,请不用担心,我将逐行解释所有的 CIL 代码。我们将检查 CIL 代码并将其翻译回一个新的 C# 程序。

ildasm — C# 反编译器

我将使用微软的 ildasm.exe(中间语言反编译器)来检查 C# 编译器创建的可执行文件。(ildasm.exe 可能已经在你的电脑上某个地方了。请在 C:\Program Files (x86)\Microsoft SDKs\Windows\... 下搜索。还有其他程序可以反编译 C# 可执行文件,例如 ILSpydotPeek.NET Reflector。)

这是当我们加载上面程序的已编译可执行文件时 ildasm.exe 显示的内容。我在右侧添加了一些 C# 注释。

5327239/msil3.png

上面是类和方法的列表。方法的通用格式是:名称 : 返回类型(参数)。我将有远见地将 ildasm.exe 给出的一些晦涩难懂的名称更改为更友好、更有意义的名称。

ildasm 名称   重命名为
<Main>d__0 类 StateMachine
<Main> static void Main()
Main private static Task SubMain()

<Main>d__0 是 C# 编译器为我们创建的一个类名称。我将把这个类重命名为 StateMachine

另外,我们有两个名称相似的方法:

  • <Main>(尖括号是方法名的一部分)
  • Main

当我们检查这两个方法的详细信息时,我们会发现方法 <Main> 是程序的入口点,而方法 Main 才是我们上面示例 C# 程序方法 Main() 中五行代码实际去向的地方。

类 Program

现在,我们可以根据 IL DASM 反编译器的信息开始构建我们已编译程序的 C# 大纲了。

namespace ConsoleAppTCSa
{
    class Program
    {
        class StateMachine  // <Main>d__0
        {
        }

        public Program()    // .ctor : void()   // the constructor
                                                // (which does nothing special
                                                // so we'll skip this)
        {
        }

        static void Main()  // <Main> : void()  // the main entrypoint
        {
        }

        private static void Display(string s)   // Display : void(string)
        {
        }

        private static void DoWork()            // DoWork : void()
        {
        }

        private static Task SubMain() // Main : class[mscorlib]System.Threading.Tasks.Task()
        {
        }

        private static void MoreWork()          // MoreWork : void()
        {
        }
    }
}

让我们逐一查看这些方法。

.ctor : void()

让我们从查看 Program 类的构造函数开始。

5327239/msilctor.png

在上面的 IL DASM 窗口中双击 .ctor : void() 行会打开一个新窗口,显示 CIL 代码。

5327239/ctor.png

我们可以看到这个构造函数只是默认构造函数,它除了调用 System.Object 的构造函数外,什么也不做,所以我们将在 C# 代码中省略它,让 C# 编译器为我们生成这个默认构造函数。

<Main> : void()

现在让我们看看方法 <Main> : void() 的代码。

5327239/msilmain.png

在 IL DASM 中双击 <Main> : void() 会得到:

5327239/main2a.png

在顶部,我们看到这是一个返回 voidstatic 方法。.entrypoint 声明这是程序开始的地方。在 C# 中,这将被调用为方法 Main()

static void Main()
{
}

在开头,我们看到 .locals,它定义了 V_0TaskAwaiter 类型。

static void Main()
{
    TaskAwaiter V_0;
}

分解第一行 CIL 代码,我们有:

         command      +-------------- return type --------------+ +---- called method ------+
            ↓         │                                         │ │                         │
IL_0000:  call        class [mscorlib]System.Threading.Tasks.Task ConsoleAppTCSa.Program.Main

我将这个方法调用的名称重命名为 SubMain(),因为我们已经有一个 Main() 方法了。因此,第一行调用我正在重命名的 SubMain() 并返回一个 Task。我们最终会有几个 Task,为了便于区分,我将有远见地将这里返回的 Task 命名为 statemachineTask

static void Main()
{
    TaskAwaiter V_0;

    Task statemachineTask = SubMain();
}

第二行获取该调用的结果,即 statemachineTask,并调用 statemachineTask.GetAwaiter()。我们看到此调用的返回类型是 TaskAwaiter

instance 关键字表示这是一个类实例而不是一个 static 方法。callvirt 类似于 call,但有一个区别是它在使用类实例之前还会检查其有效性。由于返回的 TaskAwaiter 来自 statemachineTask,我将其命名为 statemachineTaskAwaiter

static void Main()
{
    TaskAwaiter V_0;

    Task statemachineTask = SubMain();
    TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
}

(请注意,在内部,statemachineTaskAwaiterstatemachineTask 存储在一个名为 m_taskprivate 字段中。因此,TaskAwaiter 可以访问它正在执行 awaitTask。)

第三行

IL_0005:  stloc.0  // store result in local V_0

将生成的 statemachineTaskAwaiter 存储在局部变量 V_0 中。

static void Main()
{
    TaskAwaiter V_0;

    Task statemachineTask = SubMain();
    TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
    V_0 = statemachineTaskAwaiter;
}

第 4 行 ldloca.s V_0 是“加载局部变量 V_0 的地址”。结合第五行

             not    return
command     static   type +-------------------------- called method -----------------------+
 ↓            ↓       ↓   │                                                                │
call        instance void [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::GetResult()

翻译为调用 V_0.GetResult();

static void Main()
{
    TaskAwaiter V_0;

    Task statemachineTask = SubMain();
    TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
    V_0 = statemachineTaskAwaiter;
    V_0.GetResult();
}

最后一行 ret 当然是 return

static void Main()
{
    TaskAwaiter V_0;

    Task statemachineTask = SubMain();
    TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
    V_0 = statemachineTaskAwaiter;
    V_0.GetResult();
    return;
}

消除不必要的局部变量 V_0 得到:

static void Main()
{
    Task statemachineTask = SubMain();
    TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
    statemachineTaskAwaiter.GetResult();
    return;
}

Main : class [mscorlib]System.Threading.Tasks.Task()

让我们继续处理另一个 Main CIL 方法。

5327239/msilsubmain.png

在 IL DASM 中双击 Main : class [mscorlib]System.Threading.Tasks.Task() 会得到:

5327239/submain4.png

在顶部,我们看到这是一个返回 Taskprivate static 方法。

我将这个 CIL Main 方法重命名为 SubMain(),因为我们已经有一个 Main() 方法了。

private static Task SubMain()
{
}

.locals 定义了局部变量 V_0<Main>d__0 类型,这是一个类名。我很有远见地将这个类重命名为 StateMachine

private static Task SubMain()
{
    StateMachine V_0;
}

将此方法其余部分从 CIL 翻译为 C# 得到:

private static Task SubMain()
{
    // .locals init ([0] class ConsoleAppAsync0.Program/'<Main>d__0' V_0)
    StateMachine V_0;

    // IL_0000:  newobj  instance void
    //           ConsoleAppAsync0.Program/'<Main>d__0'::.ctor() ? new StateMachine()
    // IL_0005:  stloc.0 ? store result in V_0
    V_0 = new StateMachine();

    // IL_0007:  call
    // valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
    // [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
    // IL_000c:  stfld
    // valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
    // ConsoleAppAsync0.Program/'<Main>d__0'::'<>t__builder'
    V_0.Builder = new AsyncTaskMethodBuilder();

    // IL_0011:  ldloc.0   ? load local V_0
    // IL_0012:  ldc.i4.m1 ? load -1
    // IL_0013:  stfld      int32 ConsoleAppAsync0.Program/'<Main>d__0'::'<>1__state' ?
    //           store result in field V_0.State
    V_0.State = -1;

    // IL_0019:  ldflda
    // valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
    // ConsoleAppAsync0.Program/'<Main>d__0'::'<>t__builder' ?
    //                           load address of field: V_0.t__builder
    // IL_001e:  ldloca.s  V_0 ? load address of local V_0 (V_0 will be argument passed by ref)
    // IL_0020:  call      instance void
    //           [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start
    //           <class ConsoleAppAsync0.Program/'<Main>d__0'>(!!0&)
    V_0.Builder.Start(ref V_0);

    // IL_0025:  ldloc.0   ? load V_0
    // IL_0026:  ldflda    valuetype
    //                     [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
    //                     ConsoleAppAsync0.Program/'<Main>d__0'::'<>t__builder' ?
    //                     load address of field: V_0.t__builder
    // IL_002b:  call      instance class [mscorlib]System.Threading.Tasks.Task
    //                     [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::
    //                     get_Task() ? V_0.t__builder.Task;
    // IL_0030:  ret       ? return V_0.t__builder.Task;
    return V_0.Builder.Task;
}

删除 CIL 代码注释后,我们得到:

private static Task SubMain()
{
    StateMachine V_0;

    V_0 = new StateMachine();
    V_0.Builder = new AsyncTaskMethodBuilder();
    V_0.State = -1;
    V_0.Builder.Start(V_0);
    return V_0.Builder.Task;
}

V_0 重命名为 statemachine 得到:

private static Task SubMain()
{
    StateMachine statemachine = new StateMachine();
    statemachine.Builder = new AsyncTaskMethodBuilder();
    statemachine.State = -1;
    statemachine.Builder.Start(ref statemachine);
    return statemachine.Builder.Task;
}

Display : void(string)

5327239/msildisplay.png

在 IL DASM 中双击 Display : void(string) 会得到:

5327239/display1.png

这翻译为:

private static void Display(string s)
{
    Console.WriteLine("{0:hh\\:mm\\:ss\\:fff} {1}", DateTime.Now, s);
}

这会输出字符串 s,前面带有时间戳。

(注意:这里的 ldarg.0 不是“this”指针,因为这是一个 static 方法。如果这是一个实例方法(通过 new 关键字创建的),那么 ldarg.0 将是所有实例方法隐式拥有的“this”指针,作为 arg 0。(您还会在 CIL 代码的顶部附近看到 instance 一词,表示它是实例方法而不是 static 方法。))

DoWork : void()

5327239/msildowork.png

在 IL DASM 中双击 DoWork : void() 会得到:

5327239/dowork1.png

这翻译为:

private static void DoWork()
{
    Display("Enter DoWork()");
    Thread.Sleep(2000);
    Display("Exit DoWork()");
}

MoreWork : void()

5327239/msilmorework.png

在 IL DASM 中双击 MoreWork : void() 会得到:

5327239/morework1.png

这与上面的 DoWork() 类似,翻译为:

private static void MoreWork()
{
    Display("Enter MoreWork()");
    Thread.Sleep(2000);
    Display("Exit MoreWork()");
}

<Main>d__0 → class StateMachine

让我们打开 <Main>d__0 类,看看里面有什么。

5327239/msilstatemachine.png

再次,我很有远见地将这里的一些名称更改为更有意义的名称。

ildasm 名称   重命名为
<Main>d__0 类 StateMachine
<>1__state 状态
<>t__builder Builder
<>u__1 DelayTaskAwaiter(因为它是由 Task.Delay(2000) 返回的 delayTask 创建的)

我们看到这个类的通用大纲是:

class StateMachine : IAsyncStateMachine
{
    public int State;                       // 1__state
    public AsyncTaskMethodBuilder Builder;  // t__builder
    private TaskAwaiter DelayTaskAwaiter;   // u__1;

    public StateMachine() // .ctor : void() // the constructor
                          // (which does nothing special so we'll skip this)
    {
    }

    public void MoveNext()
    {
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }
}

.ctor : void()

查看 Class StateMachine 的构造函数。

5327239/msilstatemachinector.png

代码是:

5327239/statemachinector.png

我们看到这个构造函数只是默认构造函数,它除了调用 System.Object 的构造函数外,什么也不做,所以我们将在 C# 代码中省略它,让 C# 编译器为我们生成一个默认构造函数。

SetStateMachine

5327239/msilsetstatemachine.png

暂时跳过 MoveNext() 方法,让我们看看 SetStateMachine 的代码。

5327239/setstatemachine.png

我们可以看到这段代码什么也没做。唯一的指令是 IL_0000: ret,这是一个 return 语句。这个方法存在的唯一原因是因为这个类实现了 IAsyncStateMachine 接口,该接口需要它。这翻译为:

public void SetStateMachine(IAsyncStateMachine stateMachine)
{
    return;
}

(我们的 C# 版本需要是 public,因为它实现了接口要求。)

MoveNext : void()

现在我们深入研究整个程序中最复杂的方法,即 MoveNext() 方法。这是一个很长的函数,请耐心点,我们会看完的。

5327239/msilmovenext.png

这个方法的 CIL 代码是:

5327239/movenext16.png

这是 MoveNext() 方法 CIL 代码的逐行 C# 翻译:

class StateMachine : IAsyncStateMachine
{
    public int State;                       // 1__state
    public AsyncTaskMethodBuilder Builder;  // t__builder
    private TaskAwaiter DelayTaskAwaiter;   // u__1; (with foresight I'm naming
                                            // this DelayTaskAwaiter,
                                            // since it's created from delayTask.)

    public void MoveNext()
    {
        //  .locals init ([0] int32 V_0,
        //           [1] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter V_1,
        //           [2] class ConsoleAppTCSa.Program/'<Main>d__0' V_2,
        //           [3] class [mscorlib]System.Exception V_3)
        // Local variables
        Int32        V_0;  // state
        TaskAwaiter  V_1;  // delayTaskAwaiter
        StateMachine V_2;  // statemachine
        Exception    V_3;  // exception

        // IL_0000:  ldarg.0
        // IL_0001:  ldfld      int32 ConsoleAppTCSa.Program/'<Main>d__0'::'<>1__state'
        // IL_0006:  stloc.0
        V_0 = this.State;

        try  //  .try
        {
            // IL_0007:  ldloc.0
            // IL_0008:  brfalse.s  IL_000c
            if (V_0 == 0) goto IL_000c;

            // IL_000a:  br.s       IL_000e
            goto IL_000e;

            // IL_000c:  br.s       IL_0052
IL_000c:    goto IL_0052;

// ---------------------------------------------------------------------------------------------
IL_000e: // PART 1:
            // IL_000f:  call       void ConsoleAppTCSa.Program::DoWork()
            DoWork();

            // IL_0014:  nop        // no operation (do nothing,
            //                         just continue on to the next instruction)
            // IL_0015:  ldc.i4     0x7d0  // 0x7d0 = decimal 2000
            // IL_001a:  call       class [mscorlib]System.Threading.Tasks.Task
            //                      [mscorlib]System.Threading.Tasks.Task::Delay(int32)
            Task delayTask = Task.Delay(2000);

            // IL_001f:  callvirt   instance valuetype
            //           [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
            //                      [mscorlib]System.Threading.Tasks.Task::GetAwaiter()
            // IL_0024:  stloc.1
            V_1 = delayTask.GetAwaiter();

            // IL_0025:  ldloca.s   V_1
            // IL_0027:  call       instance bool
            //      [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::get_IsCompleted()
            // IL_002c:  brtrue.s   IL_006e
            if (V_1.IsCompleted) goto IL_006e;

            // IL_002e:  ldarg.0    // this
            // IL_002f:  ldc.i4.0   // load a zero (0)
            // IL_0030:  dup        // duplicate that 0
            // IL_0031:  stloc.0    // store 0 in V_0
            // IL_0032:  stfld      int32 ConsoleAppTCSa.Program/'<Main>d__0'::'<>1__state'
            // and store the other 0 in this.State;
            V_0 = 0;
            this.State = 0;

            // IL_0037:  ldarg.0    // this
            // IL_0038:  ldloc.1    // load local V_1
            // IL_0039:  stfld
            //           valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
            //                      ConsoleAppTCSa.Program/'<Main>d__0'::'<>u__1'
            this.DelayTaskAwaiter = V_1;

            // IL_003e:  ldarg.0    // this
            // IL_003f:  stloc.2    // store in local V_2
            V_2 = this;

            // IL_0040:  ldarg.0    // this
            // IL_0041:  ldflda     valuetype
            //           [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
            // ConsoleAppTCSa.Program/'<Main>d__0'::'<>t__builder'
            // IL_0046:  ldloca.s   V_1  // load address of local V_1
            // (will be argument passed by ref)
            // IL_0048:  ldloca.s   V_2  // load address of local V_2
            //           (will be argument passed by ref)
            // IL_004a:  call       instance void
            // [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::
            // AwaitUnsafeOnCompleted<valuetype
            //                        ?[mscorlib]System.Runtime.CompilerServices.TaskAwaiter,
            // class ConsoleAppTCSa.Program/'<Main>d__0'>(!!0&, !!1&)
            this.Builder.AwaitUnsafeOnCompleted(ref V_1, ref V_2);

            // IL_004f:  nop
            // IL_0050:  leave.s    IL_00bb  // leave try/catch block. Goto IL_00bb
            goto IL_00bb;

// ---------------------------------------------------------------------------------------------
IL_0052:  // PART 2:
            // IL_0052:  ldarg.0    // this
            // IL_0053:  ldfld
            // valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
            // ConsoleAppTCSa.Program/'<Main>d__0'::'<>u__1'
            // IL_0058:  stloc.1    // store in local V_1
            V_1 = this.DelayTaskAwaiter;

            // IL_0059:  ldarg.0    // this
            // IL_005a:  ldflda     valuetype
            // [mscorlib]System.Runtime.CompilerServices.TaskAwaiter ConsoleAppTCSa.Program/
            // '<Main>d__0'::'<>u__1'
            // IL_005f:  initobj    [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
            this.DelayTaskAwaiter = default;  // (release previous DelayTaskAwaiter
                                              // for garbage collection)

            // IL_0065:  ldarg.0    // this
            // IL_0066:  ldc.i4.m1  // -1
            // IL_0067:  dup        // -1
            // IL_0068:  stloc.0    // V_0 = -1;
            // IL_0069:  stfld      int32 ConsoleAppTCSa.Program/
            //                      '<Main>d__0'::'<>1__state' // this.State = -1;
            V_0 = -1;
            this.State = -1;
IL_006e:
            // IL_006e:  ldloca.s   V_1
            // IL_0070:  call       instance void [mscorlib]System.Runtime.
            //                      CompilerServices.TaskAwaiter::GetResult()
            V_1.GetResult();

            // IL_0075:  nop        // no operation
            // IL_0076:  call       void ConsoleAppTCSa.Program::MoreWork()
            MoreWork();

            // IL_007b:  nop        // no operation
            // IL_007c:  ldstr      "Press Enter: "  // load string
            // IL_0081:  call       void [mscorlib]System.Console::Write(string)
            Console.Write("Press Enter: ");

            // IL_0086:  nop        // no operation
            // IL_0087:  call       string [mscorlib]System.Console::ReadLine()
            // IL_008c:  pop        // discard result of ReadLine()
            Console.ReadLine();

            // IL_008d:  leave.s    IL_00a7  // leave try/catch block. Goto IL_00a7
            goto IL_00a7;
        } // end .try
// ---------------------------------------------------------------------------------------------
        catch (Exception exception) // catch [mscorlib]System.Exception
        {
            // IL_008f:  stloc.3    // store in local V_3
            V_3 = exception;

            // IL_0090:  ldarg.0    // this
            // IL_0091:  ldc.i4.s   -2  // load -2
            // IL_0093:  stfld
            // int32 ConsoleAppTCSa.Program/'<Main>d__0'::'<>1__state'  // store in field
            this.State = -1;

            // IL_0098:  ldarg.0    // this
            // IL_0099:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.
            // AsyncTaskMethodBuilder ConsoleAppTCSa.Program/'<Main>d__0'::'<>t__builder'
            // IL_009e:  ldloc.3    // load local V_3 (will be passed as argument)
            // IL_009f:  call       instance void [mscorlib]System.Runtime.CompilerServices.
            // AsyncTaskMethodBuilder::SetException(class [mscorlib]System.Exception)
            this.Builder.SetException(V_3);

            // IL_00a4:  nop        // no operation
            // IL_00a5:  leave.s    IL_00bb
            goto IL_00bb:
        } // end handler
// ---------------------------------------------------------------------------------------------
IL_00a7:
        // IL_00a7:  ldarg.0        // this
        // IL_00a8:  ldc.i4.s   -2  // load -2
        // IL_00aa:  stfld      int32 ConsoleAppTCSa.Program/'<Main>d__0'::'<>1__state'
        this.State = -2;

        // IL_00af:  ldarg.0        // this
        // IL_00b0:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.
        // AsyncTaskMethodBuilder ConsoleAppTCSa.Program/'<Main>d__0'::'<>t__builder'
        // IL_00b5:  call       instance void [mscorlib]System.Runtime.
        // CompilerServices.AsyncTaskMethodBuilder::SetResult()
        this.Builder.SetResult();

        // IL_00ba:  nop            // no operation

IL_00bb:
        // IL_00bb:  ret
        return;
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
        return;
    }
}

删除所有 CIL 代码注释后,我们得到:

class StateMachine : IAsyncStateMachine
{
    public int State;                       // 1__state
    public AsyncTaskMethodBuilder Builder;  // t__builder
    private TaskAwaiter DelayTaskAwaiter;   // u__1;

    public void MoveNext()
    {
        // Local variables
        Int32        V_0;  // state
        TaskAwaiter  V_1;  // delayTaskAwaiter
        StateMachine V_2;  // statemachine
        Exception    V_3;  // exception

        V_0 = this.State;

        try
        {
            if (V_0 == 0) goto IL_000c;
            goto IL_000e;
IL_000c:    goto IL_0052;

// ---------------------------------------------------------------------------------------------
IL_000e: // PART 1:
            DoWork();
            Task delayTask = Task.Delay(2000);
            V_1 = delayTask.GetAwaiter();
            if (V_1.IsCompleted) goto IL_006e;
            V_0 = 0;
            this.State = 0;
            this.DelayTaskAwaiter = V_1;
            V_2 = this;
            this.Builder.AwaitUnsafeOnCompleted(ref V_1, ref V_2);
            goto IL_00bb;

// ---------------------------------------------------------------------------------------------
IL_0052:  // PART 2:
            V_1 = this.DelayTaskAwaiter;
            this.DelayTaskAwaiter = default;  // (release previous DelayTaskAwaiter
                                              // for garbage collection)
            V_0 = -1;
            this.State = -1;
IL_006e:    V_1.GetResult();
            MoreWork();
            Console.Write("Press Enter: ");
            Console.ReadLine();
            goto IL_00a7;
        }
// ---------------------------------------------------------------------------------------------
        catch (Exception ex)
        {
            V_3 = ex;
            this.State = -1;
            this.Builder.SetException(V_3);
            goto IL_00bb;
        }
// ---------------------------------------------------------------------------------------------
IL_00a7:
        this.State = -2;
        this.Builder.SetResult();
IL_00bb:
        return;
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
        return;
    }
}

我们可以通过检查 try 块顶部的这三行代码来进一步简化:

            if (V_0 == 0) goto IL_000c;
            goto IL_000e;
IL_000c:    goto IL_0052;

如果 V_0 为零,则跳转到 IL_000c,后者又跳转到 IL_0052;否则跳转到 IL_000e。这是一个 switch 语句,所以我们现在来简化它。

class StateMachine : IAsyncStateMachine
{
    public int State;                       // 1__state
    public AsyncTaskMethodBuilder Builder;  // t__builder
    private TaskAwaiter DelayTaskAwaiter;   // u__1;

    public void MoveNext()
    {
        // Local variables
        Int32        V_0;  // state
        TaskAwaiter  V_1;  // delayTaskAwaiter
        StateMachine V_2;  // statemachine
        Exception    V_3;  // exception

        V_0 = this.State;

        try
        {
            switch (V_0)
            {
                default:  // PART 1:
                    DoWork();
                    Task delayTask = Task.Delay(2000);
                    V_1 = delayTask.GetAwaiter();
                    if (V_1.IsComplete) goto IL_006e;
                    V_0 = 0;
                    this.State = 0;
                    this.DelayTaskAwaiter = V_1;
                    V_2 = this;
                    this.Builder.AwaitUnsafeOnCompleted(ref V_1, ref V_2);
                    return;

                case 0:  // PART 2:
                    V_1 = this.DelayTaskAwaiter;
                    this.DelayTaskAwaiter = default;  // (release previous DelayTaskAwaiter
                                                      // for garbage collection)
                    V_0 = -1;
                    this.State = -1;
IL_006e:            V_1.GetResult();
                    MoreWork();
                    Console.Write("Press Enter: ");
                    Console.ReadLine();
                    break;
            }
        }
        catch (Exception ex)
        {
            V_3 = ex;
            this.State = -1;
            this.Builder.SetException(V_3);
            return;
        }

        this.State = -2;
        this.Builder.SetResult();
        return;
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
        return;
    }
}

重命名局部变量 V_0V_1V_2V_3 以获得更好的名称,并将标签 IL_006e: 更改为 IsCompleted:

class StateMachine : IAsyncStateMachine
{
    public int State;                       // 1__state
    public AsyncTaskMethodBuilder Builder;  // t__builder
    private TaskAwaiter DelayTaskAwaiter;   // u__1;

    public void MoveNext()
    {
        // Local variables
        Int32        state;            // V_0
        TaskAwaiter  delayTaskAwaiter; // V_1
        StateMachine statemachine;     // V_2
        Exception    exception;        // V_3

        state = this.State;

        try
        {
            switch (state)
            {
                default:  // PART 1:
                    DoWork();
                    Task delayTask = Task.Delay(2000);
                    delayTaskAwaiter = delayTask.GetAwaiter();
                    if (delayTaskAwaiter.IsCompleted) goto IsCompleted;
                    state = 0;
                    this.State = 0;
                    this.DelayTaskAwaiter = delayTaskAwaiter;
                    statemachine = this;  // V_2
                    this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine);
                    return;

                case 0:  // PART 2:
                    delayTaskAwaiter = this.DelayTaskAwaiter;
                    this.DelayTaskAwaiter = default;  // (release previous DelayTaskAwaiter
                                                      // for garbage collection)
                    state = -1;
                    this.State = -1;
IsCompleted:        delayTaskAwaiter.GetResult();
                    MoreWork();
                    Console.Write("Press Enter: ");
                    Console.ReadLine();
                    break;
            }
        }
        catch (Exception ex)
        {
            exception = ex;
            this.State = -1;
            this.Builder.SetException(exception);
            return;
        }

        this.State = -2;
        this.Builder.SetResult();
        return;
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
        return;
    }
}

我们可以通过本地化变量 statemachine 的定义并消除名为 exception 的不必要局部变量来进一步简化。

class StateMachine : IAsyncStateMachine
{
    public int State;                       // 1__state
    public AsyncTaskMethodBuilder Builder;  // t__builder
    private TaskAwaiter DelayTaskAwaiter;   // u__1;

    public void MoveNext()
    {
        // Local variables
        Int32           state;            // V_0
        TaskAwaiter     DelayTaskAwaiter; // V_1
        StateMachine statemachine;     // V_2
        Exception    exception;        // V_3

        state = this.State;

        try
        {
            switch (state)
            {
                default:  // PART 1:
                    DoWork();
                    Task delayTask = Task.Delay(2000);
                    delayTaskAwaiter = delayTask.GetAwaiter();
                    if (delayTaskAwaiter.IsCompleted) goto IsCompleted;
                    state = 0;
                    this.State = 0;
                    this.DelayTaskAwaiter = delayTaskAwaiter;
                    StateMachine statemachine = this;  // V_2
                    this.Builder.AwaitUnsafeOnCompleted(ref awaiter, ref statemachine);
                    return;

                case 0:  // PART 2:
                    delayTaskAwaiter = this.DelayTaskAwaiter;
                    this.DelayTaskAwaiter = default;  // (release previous DelayTaskAwaiter
                                                      // for garbage collection)
                    state = -1;
                    this.State = -1;
IsCompleted:        delayTaskAwaiter.GetResult();
                    MoreWork();
                    Console.Write("Press Enter: ");
                    Console.ReadLine();
                    break;
            }
        }
        catch (Exception ex)
        {
            exception = ex;
            this.State = -1;
            this.Builder.SetException(ex);
            return;
        }

        this.State = -2;
        this.Builder.SetResult();
        return;
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
        return;
    }
}

编译后程序的忠实 C# 表示

将所有这些内容放在一起,并按逻辑顺序排列方法,就得到了我们完整的 C# 程序。

using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleAppTCSa
{
    class Program
    {
        static void Main()                      // <Main> : void  // the main entrypoint
        {
            Task statemachineTask = SubMain();
            TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
            statemachineTaskAwaiter.GetResult();
            return;
        }

        private static Task SubMain()          // Main : class
                                               // [mscorlib]System.Threading.Tasks.Task()
        {
            StateMachine statemachine = new StateMachine();
            statemachine.Builder = new AsyncTaskMethodBuilder();
            statemachine.State = -1;
            statemachine.Builder.Start(ref statemachine);
            return statemachine.Builder.Task;
        }

        class StateMachine : IAsyncStateMachine     // <Main>d__0
        {
            public int State;                       // 1__state
            public AsyncTaskMethodBuilder Builder;  // t__builder
            private TaskAwaiter DelayTaskAwaiter;   // u__1;

            public void MoveNext()
            {
                // Local variables
                Int32       state;                  // V_0
                TaskAwaiter delayTaskAwaiter;       // V_1

                state = this.State;

                try
                {
                    switch (state)
                    {
                        default:  // case -1
                            DoWork();                                            // ┐ Part 1
                            Task delayTask = Task.Delay(2000);                   // │
                            delayTaskAwaiter = delayTask.GetAwaiter();           // │
                            if (delayTaskAwaiter.IsCompleted) goto IsCompleted;  // │
                            state = 0;                                           // │
                            this.State = 0;                                      // │
                            this.DelayTaskAwaiter = delayTaskAwaiter;            // │
                            StateMachine statemachine = this;                    // │
                            this.Builder.AwaitUnsafeOnCompleted                  // │
                                 (ref delayTaskAwaiter, ref statemachine);       // │
                            return;                                              // ┘

                        case 0:
                            delayTaskAwaiter = this.DelayTaskAwaiter;            // ┐ Part 2
                            this.DelayTaskAwaiter = default;                     // │
                            state = -1;                                          // │
                            this.State = -1;                                     // │
               IsCompleted: delayTaskAwaiter.GetResult();                        // │
                            MoreWork();                                          // │
                            Console.Write("Press Enter: ");                      // │
                            Console.ReadLine();                                  // ┘
                            break;
                    }
                }
                catch (Exception ex)
                {
                    this.State = -1;
                    this.Builder.SetException(ex);
                    return;
                }

                this.State = -2;
                this.Builder.SetResult();
                return;
            }

            public void SetStateMachine(IAsyncStateMachine stateMachine)
            {
                return;
            }
        }

        private static void Display(string s)  // Display : void()
        {
            Console.WriteLine("{0:hh\\:mm\\:ss\\:fff} {1}", DateTime.Now, s);
        }

        private static void DoWork()           // DoWork : void()
        {
            Display("Enter DoWork()");
            Thread.Sleep(2000);
            Display("Exit DoWork()");
        }

        private static void MoreWork()         // MoreWork : void()
        {
            Display("Enter MoreWork()");
            Thread.Sleep(2000);
            Display("Exit MoreWork()");
        }
    }
}

这是编译器编译我们原始程序时生成的代码的忠实 C# 表示。这段代码可以编译并正常运行,并且执行的操作与我们开始时的原始代码相同。主要区别在于这个版本不使用 async/await 关键字。

简化

让我们简化我们编译后程序的忠实 C# 表示,以揭示正在发生的事情的基本概念。

简化 Main()

从方法 Main() 开始。

static void Main()              // <Main> : void  // the main entrypoint
{
    Task statemachineTask = SubMain();
    TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
    statemachineTaskAwaiter.GetResult();
    return;
}

在此方法中,第二行

TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter()

返回一个 TaskAwaiter。如果我们查看 https://referencesource.microsoft.comTask.GetAwaiter() 的代码,我们会发现在内部,返回的 TaskAwaiter 在名为 m_taskprivate 字段中保存了指向任务 statemachineTask 的指针。

第三行

statemachineTaskAwaiter.GetResult()

内部调用 statemachineTask.Wait(),然后检查任务的结束状态。我们可以简化这两行代码并消除 TaskAwaiter

static void Main()                      // <Main> : void  // the main entrypoint
{
    Task statemachineTask = SubMain();
    TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
    statemachineTaskAwaiter.GetResult();
    statemachineTask.Wait();
    if (!statemachineTask.IsRanToCompletion) ThrowForNonSuccess(statemachineTask);
    return;
}

statemachineTask.Wait() 是通过使用 Monitor.Wait(m_lock)Monitor.PulseAll(m_lock) 完成的。调用 Monitor.Wait(m_lock) 直到另一个线程调用 Monitor.PulseAll(m_lock) 才会返回。

首先,我们将 continuation action 设置为运行 Monitor.PulseAll(m_lock),然后调用 Monitor.Wait(m_lock) 并等待。

static void Main()                      // <Main> : void  // the main entrypoint
{
    private volatile object m_lock = new Object();

    Task statemachineTask = SubMain();
    statemachineTask.Wait();
    statemachineTask.continuationObject = new Action(() =>
        lock (m_lock)
        {
            Monitor.PulseAll(m_lock));
        });

    lock (m_lock)
    {
        Monitor.Wait(m_lock);
    }

    if (!statemachineTask.IsRanToCompletion) ThrowForNonSuccess(statemachineTask);
    return;
}

statemachineTask 转换为已完成状态时,它会运行 continuation action,该 action 调用 Monitor.PulseAll(m_lock),从而使我们从等待中释放出来。

简化 MoveNext()

继续处理 StateMachine 类的方法 MoveNext(),调用 if (delayTaskAwaiter.IsCompleted) goto IsCompleted; 只是一个优化,以跳过等待 delayTask 完成(如果它已经完成)。让我们移除此优化和不必要的优化局部变量。

class StateMachine : IAsyncStateMachine     // <Main>d__0
{
    public int State;                       // 1__state
    public AsyncTaskMethodBuilder Builder;  // t__builder
    private TaskAwaiter DelayTaskAwaiter;   // u__1;

    public void MoveNext()
    {
        // Local variables
        Int32       state;                  // V_0
        TaskAwaiter delayTaskAwaiter;       // V_1

        state = this.State;

        try
        {
            switch (this.State)
            {
                default:  // case -1
                    DoWork();                                                     // ┐ Part 1
                    Task delayTask = Task.Delay(2000);                            // │
                    TaskAwaiter delayTaskAwaiter = delayTask.GetAwaiter();        // │
                    if (delayTaskAwaiter.IsCompleted) goto IsCompleted;           // │
                    state = 0;                                                    // │
                    this.State = 0;                                               // │
                    this.DelayTaskAwaiter = delayTaskAwaiter;                     // │
                    StateMachine statemachine = this;                             // │
                    this.Builder.AwaitUnsafeOnCompleted                           // │
                         (ref delayTaskAwaiter, ref statemachine);                // │
                    return;                                                       // ┘

                case 0:
                    delayTaskAwaiter = this.DelayTaskAwaiter;                     // ┐ Part 2
                    this.DelayTaskAwaiter = default;                              // │
                    state = -1;                                                   // │
                    this.State = -1; // unnecessary                               // │
       IsCompleted: this.DelayTaskAwaiter.GetResult();                            // │
                    MoreWork();                                                   // │
                    Console.Write("Press Enter: ");                               // │
                    Console.ReadLine();                                           // ┘
                    break;
            }
        }
        catch (Exception ex)
        {
            this.State = -1;  // unnecessary
            this.Builder.SetException(ex);
            return;
        }

        this.State = -2;  // unnecessary
        this.Builder.SetResult();
        return;
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
        return;
    }
}

这留下了:

class StateMachine : IAsyncStateMachine     // <Main>d__0
{
    public int State;                       // 1__state
    public AsyncTaskMethodBuilder Builder;  // t__builder

    public void MoveNext()
    {
        try
        {
            switch (this.State)
            {
                default:  // case -1
                    DoWork();                                               // ┐ Part 1
                    Task delayTask = Task.Delay(2000);                      // │
                    TaskAwaiter delayTaskAwaiter = delayTask.GetAwaiter();  // │
                    this.State = 0;                                         // │
                    StateMachine statemachine = this;                       // │
                    this.Builder.AwaitUnsafeOnCompleted                     // │
                         (ref delayTaskAwaiter, ref statemachine);          // │
                    return;                                                 // ┘

                case 0:
                    MoreWork();                                             // ┐ Part 2
                    Console.Write("Press Enter: ");                         // │
                    Console.ReadLine();                                     // ┘
                    break;
            }
        }
        catch (Exception ex)
        {
            this.Builder.SetException(ex);
            return;
        }

        this.Builder.SetResult();
        return;
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
        return;
    }
}

检查 MoveNext() switch 语句的 default: 情况,它运行 Part 1。

default:  // case -1
    DoWork();                                                                   // ┐ Part 1
    Task delayTask = Task.Delay(2000);                                          // │
    TaskAwaiter delayTaskAwaiter = delayTask.GetAwaiter();                      // │
    this.State = 0;                                                             // │
    StateMachine statemachine = this;                                           // │
    this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine);// │
    return;                                                                     // ┘

行:

this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine);

设置在 delayTask 完成时运行的 continuation。(回想一下,delayTaskAwaiter 在名为 m_taskprivate 字段中持有 delayTask。)当 delayTask 完成时运行的 continuation 是 statemachine.MoveNext()。我们可以概念上将此行替换为:

delayTask.continuationObject = new Action(() => this.MoveNext());
default:  // case -1
    DoWork();                                                                   // ┐ Part 1
    Task delayTask = Task.Delay(2000);                                          // │
    TaskAwaiter delayTaskAwaiter = delayTask.GetAwaiter();                      // │
    this.State = 0;                                                             // │
    StateMachine statemachine = this;                                           // │
    this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine);// │
    delayTask.continuationObject = new Action(() => this.MoveNext());           // │
    return;                                                                     // ┘

简化 SubMain()

SubMain() 中,行 statemachine.Builder.Start(ref statemachine) 基本上调用 statemachine.MoveNext()

private static Task SubMain()          // Main : class [mscorlib]System.Threading.Tasks.Task()
{
    StateMachine statemachine = new StateMachine();
    statemachine.Builder = new AsyncTaskMethodBuilder();
    statemachine.State = -1;
    statemachine.Builder.Start(ref statemachine);
    statemachine.MoveNext()
    return statemachine.Builder.Task;
}

return statemachine.Builder.Task; 返回一个由 Builder 创建的新任务。我将其称为 statemachineTask。此任务实际上不运行任何内容,它仅用作信号,指示状态机何时完成,以及作为存储结果值(如果存在)的地方。此任务在 MoveNext() 的两个地方被操作。

  • this.Builder.SetResult();
    这会将 statemachineTask 的状态设置为 "RanToCompletion" 并调用 statemachineTask.continuationObject action。
  • this.Builder.SetException(ex);
    当状态机运行 Part 1 或 Part 2 时抛出异常时,将调用此方法。它会将异常 ex 添加到 statemachineTask.m_exceptionsHolder(这是一个异常列表),将 statemachineTask 的状态设置为 "Faulted",然后调用 statemachineTask.continuationObject action。

我将 statemachineTaskBuilder 中提取出来并放入 MoveNext()。然后,我们将不再需要 Builder

private static Task SubMain()          // Main : class [mscorlib]System.Threading.Tasks.Task()
{
    StateMachine statemachine = new StateMachine();
    statemachine.Builder = new AsyncTaskMethodBuilder();
    statemachine.State = -1;
    statemachine.MoveNext();
    return statemachine.Builder.Task;
    return statemachine.statemachineTask;
}

class StateMachine : IAsyncStateMachine         // <Main>d__0
{
    public int State;                           // 1__state
    public AsyncTaskMethodBuilder Builder;      // t__builder
    public Task statemachineTask = new Task();  // was Builder.Task

    public void MoveNext()
    {
        try
        {
            switch (this.State)
            {
                default:  // case -1
                    DoWork();                                           // ┐ Part 1
                    Task delayTask = Task.Delay(2000);                  // │
                    this.State = 0;                                     // │
                    delayTask.continuationObject =                      // │
                           new Action(() => this.MoveNext());           // │
                    return;                                             // ┘

                case 0:
                    MoreWork();                                         // ┐ Part 2
                    Console.Write("Press Enter: ");                     // │
                    Console.ReadLine();                                 // ┘
                    break;
            }
        }
        catch (Exception ex)
        {
            this.Builder.SetException(ex);
            this.statemachineTask.m_stateFlags = m_stateFlags |
                      TASK_STATE_FAULTED; // set task state to "Faulted"
            Action action = this.statemachineTask.continuationObject as Action;
            action();
            return;
        }

        this.Builder.SetResult();
        this.statemachineTask.m_stateFlags = m_stateFlags |
             TASK_STATE_RAN_TO_COMPLETION; // set task state to "RanToCompletion"
        Action action = this.statemachineTask.continuationObject as Action;
        action();
        return;
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
        return;
    }
}

简化的 C# 表示

将所有这些整合在一起,我们得到了程序的完整简化版本,它更好地展示了 await 语句在底层发生了什么。

using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleAppTCSa
{
    class Program
    {
        static void Main()                      // <Main> : void  // the main entrypoint
        {
            private volatile object m_lock = new Object();

            Task statemachineTask = SubMain();

            statemachineTask.continuationObject = new Action(() =>
                lock (m_lock)
                {
                    Monitor.PulseAll(m_lock));
                });

            lock (m_lock)
            {
                Monitor.Wait(m_lock);
            }

            if (!statemachineTask.IsRanToCompletion) ThrowForNonSuccess(statemachineTask);
            return;
        }

        private static Task SubMain()          // Main : class
                                               // [mscorlib]System.Threading.Tasks.Task()
        {
            StateMachine statemachine = new StateMachine();
            statemachine.State = -1;
            statemachine.MoveNext();
            return statemachine.statemachineTask;
        }

        class StateMachine : IAsyncStateMachine         // <Main>d__0
        {
            public int State;                           // 1__state
            public Task statemachineTask = new Task();  // was Builder.Task

            public void MoveNext()
            {
                try
                {
                    switch (this.State)
                    {
                        default:  // case -1
                            DoWork();                                             // ┐ Part 1
                            Task delayTask = Task.Delay(2000);                    // │
                            this.State = 0;                                       // │
                            delayTask.continuationObject =                        // │
                                       new Action(() => this.MoveNext());         // │
                            return;                                               // ┘

                        case 0:
                            MoreWork();                                           // ┐ Part 2
                            Console.Write("Press Enter: ");                       // │
                            Console.ReadLine();                                   // ┘
                            break;
                    }
                }
                catch (Exception ex)
                {
                    this.statemachineTask.m_stateFlags = m_stateFlags |
                            TASK_STATE_FAULTED; // set task state to "Faulted"
                    Action action = this.statemachineTask.continuationObject as Action;
                    action();
                    return;
                }

                this.statemachineTask.m_stateFlags = m_stateFlags |
                     TASK_STATE_RAN_TO_COMPLETION; // set task state to "RanToCompletion"
                Action action = this.statemachineTask.continuationObject as Action;
                action();
                return;
            }

            public void SetStateMachine(IAsyncStateMachine stateMachine)
            {
                return;
            }
        }

        private static void Display(string s)  // Display : void()
        {
            Console.WriteLine("{0:hh\\:mm\\:ss\\:fff} {1}", DateTime.Now, s);
        }

        private static void DoWork()           // DoWork : void()
        {
            Display("Enter DoWork()");
            Thread.Sleep(2000);
            Display("Exit DoWork()");
        }

        private static void MoreWork()         // MoreWork : void()
        {
            Display("Enter MoreWork()");
            Thread.Sleep(2000);
            Display("Exit MoreWork()");
        }
    }
}

虽然此程序无法编译,因为它直接像访问 public 字段一样引用了一些 private 字段,但我们仍然可以追踪上述程序如果运行会发生什么。这将为我们提供深入了解 await 关键字工作原理的洞察。

追踪 await 期间发生的情况

遵循上面的代码,我们现在可以追踪程序的流程。

第一部分

  • Main() 调用 SubMain()
    • SubMain()
      1. 创建 statemachine
      2. statemachine 初始化为 State = -1
      3. 调用 statemachine.MoveNext()
        1. MoveNext() 看到 this.State == -1 并跳转到 case default:,在那里运行 Part 1。
          1. 调用 DoWork()
          2. 调用 Task.Delay(2000)
            • Task.Delay(2000) 设置一个 2 秒后过期的计时器并返回 delayTask
          3. this.State 被设置为 0(因此下次调用 MoveNext() 时将运行 Part 2)。
          4. delayTask.continuationObject 被设置为 this.MoveNext()
          5. 返回到 SubMain()
      4. SubMain()statemachine.statemachineTask 返回给 Main()
  • 此时,我们已全部设置好。接下来我们所要做的就是放松,什么也不做。
“你并不总是需要完成很多事情。有时,停止工作,放松下来,什么都不做,是完全可以的,也是绝对必要的。” — Lori Deschene, Tiny Buddha 网站

程序的其余部分将由 delayTask 在计时器到期时处理。delayTask 的 continuation action 被设置为运行 MoveNext(),它将运行我们代码的 Part 2。

如果这是一个 GUI 程序,并且 Main() 实际上是一个 Button_Click() 事件处理程序,那么我们将忽略返回的 statemachineTask,直接返回到 Message Loop,让 Message Loop 处理 Message Queue 中的其他消息。由于我选择了控制台程序作为本次演示,Main() 在此时不会返回,因为程序将结束。相反,Main() 首先等待返回的 statemachineTask 完成。

  • Main() 等待 stateMachine 完成。
    1. statemachineTask.continuationObject 设置为在 statemachineTask 转换为完成状态时运行 Monitor.PulseAll(m_lock)
    2. 调用 Monitor.Wait(m_lock)
      这会将 Main 线程置于等待状态。Main 线程的状态变为 "SleepJoinWait",并且 Main 线程从接收 CPU 时间的“运行”队列中移除,放入“等待”队列。由于线程不再在运行队列中,它不会再获得 CPU 时间。

第二部分

“静静地坐着,什么也不做,春天来了,草自己就长了。” — 禅宗谚语
  • 过了一会儿,2 秒计时器到期,一个 ThreadPool 线程运行了为处理计时器到期而设置的代码。这段代码调用 delayTask.continuationObject action,即 MoveNext()(这是 MoveNext() Part 1 调用 Task.Delay(2000) 并将 delayTask 返回给它后设置的)。

    (如果这是一个 GUI 程序,则不是直接调用 MoveNext(),而是 continuation action 会稍微复杂一些,首先检查是否需要 GUI 线程上运行 continuation action,如果需要,它会将 MoveNext() 排列到 Message Queue,以便 MoveNext() 在 GUI 线程上运行。)
    1. MoveNext() 检查 this.State,发现 this.State == 0,并跳转到 case 0:,在那里运行 Part 2。
      1. 调用 MoreWork()
      2. 调用 Console.Write("Press Enter: ")
      3. 调用 Console.ReadLine()
        1. 线程等待用户按下 Enter 键。
        2. 用户按下 Enter 键。
        3. Console.ReadLine() 返回。
      4. break;

      现在我们已经完成了 Part 1 和 Part 2 的运行。接下来只剩下清理工作了。

    2. statemachineTask 的状态设置为 "RanToCompletion"
    3. 调用 statemachineTask.continuationAction,即 Monitor.PulseAll()(这是 Main()SubMain()statemachineTask 返回给它时之前设置的)。
      1. Monitor.PulseAll(m_lock) 将之前通过 Monitor.Wait(m_lock) 挂起的线程,将其状态从 "WaitSleepJoin" 改回 "Running",并将线程返回到“运行”队列,在那里它将再次开始接收 CPU 时间片。
      2. Monitor.PulseAll(m_lock) 返回到调用它的 MoveNext()
    4. MoveNext() 返回给它的调用者,即 ThreadPool,释放了线程。
  • 与此同时,之前通过 Monitor.Wait(m_lock) 等待的 Main 线程已返回到“运行”队列并开始再次运行。Main 线程检查 statemachineTask 的状态,看它是否已成功运行完成,事实确实如此。
  • Main 线程的下一条(也是最后一条)语句是 return,它返回到调用 Main() 的操作系统。
  • 操作系统执行清理并终止程序。

您可能会注意到这里存在潜在的竞态条件,因为此时我们不知道 ThreadPool 线程是否已完成任务并返回到 ThreadPool。事实证明,释放 Main 线程是 ThreadPool 线程需要做的最后一件事。一旦它释放了 Main 线程,它的工作就完成了。它之后不会再做任何有用的事情。Main 线程可以继续执行清理工作,我们不再关心那个 ThreadPool 线程或它的状态。

这就是对运行这个看似简单的 5 行程序时发生的一切的详细演练。

public static async Task Main()
{
    DoWork();                        // ┐ Part 1
    await Task.Delay(2000);          // ┘

    MoreWork();                      // ┐ Part 2
    Console.Write("Press Enter: ");  // │
    Console.ReadLine();              // ┘
}

在 Button_Click 事件处理程序中使用 await

现在让我们看看在 WPF Button_Click() 事件处理程序方法中使用 await 时会发生什么。假设我们有一个方法看起来像这样:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    DoWork();                        // ┐ Part 1
    await Task.Delay(2000);          // ┘

    MoreWork();                      // Part 2
}

这与我们上面开始的 5 行控制台程序类似。一个不同之处在于,这里传递给我们两个参数:sendere。在这两个参数在我们的示例代码中都没有被使用;但是,如果我们想使用它们,它们是可以使用的。

编译这段代码,然后使用 IL DISM 反编译器进行反编译,然后将 CIL 代码翻译回 C#,我们会发现编译器生成了等同于以下内容的 CIL 代码:

private void Button_Click(object sender, RoutedEventArgs e)
{
    Statemachine stateMachine = new Statemachine();
    statemachine.Builder = new AsyncTaskMethodBuilder();
    statemachine.This = this;
    statemachine.Sender = sender;
    statemachine.E = e;
    statemachine.State = -1;
    statemachine.Builder.Start(ref statemachine);
    return;
}

这几乎与我们上面示例中的 SubMain() 方法相同,只是代码还设置了 statemachine 中的三个额外字段。

  1. this
  2. sender
  3. e

另一个不同之处在于该方法返回 void,而不是像控制台版本那样返回 statemachine.Builder.Task。在此场景下,任务 statemachine.Builder.Task 被忽略,线程返回到 Message Loop,继续处理其他消息。

其他一切都和以前一样。当我们从 Button_Click() 返回时,delayTaskcontinuationObject 被设置为运行 MoveNext(),并且 statemachine.State == 0,因此它被设置为运行 statemachine 的下一部分(Part 2)。

还有一个额外的区别:continuationObject 现在是一个特殊的类,它包含 continuation ActionSynchronizationContext,它帮助我们回到 GUI 线程(如果我们最初在 GUI 线程上并且需要返回)。如果我们不需要跳回到 GUI 线程,则直接调用 MoveNext();如果需要跳回到 GUI 线程,则将 MoveNext() 排列到 Message Queue。这如何实现,以及对 Message QueueMessage Loop 的解释,在我另一篇论文中有说明:Async/Await 解释(附带图表和示例):如何回到 UI 线程

追踪 AwaitUnsafeOnCompleted

以下详细信息适用于希望深入了解 WPF Button_Click() 事件处理程序如何设置任务 continuation 的用户。

this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine); 创建 SynchronizationContextAwaitTaskContinuation 类的实例,并将其存储为 task.continuationObjectTask 类中执行此 task.continuationObject 的代码检查存储的对象是否是 TaskContinuationSynchronizationContextAwaitTaskContinuation 从其派生),如果是,则调用 taskContinuation.Run() 方法。Run() 方法负责在正确线程上运行 continuation action。continuation action 仍然是 statemachine.MoveNext()

追踪对 AwaitUnsafeOnCompleted 的调用,该调用将 delayTaskAwaiter 作为 awaiter 传递,并回想 delayTaskAwaiter 在局部字段 m_task 中持有 delayTask,内部调用如下:

this.Builder.AwaitUnsafeOnCompleted(ref awaiter, ref statemachine);
 awaiter.UnsafeOnCompleted(continuation);
  TaskAwaiter.OnCompletedInternal(m_task, continuation,
              continueOnCapturedContext:true, flowExecutionContext:false);
    task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext,…
     if (continueOnCapturedContext)
      tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, continuationAction,…

tc 是我们的 delayTask 任务 continuation,它被设置为 SynchronizationContextAwaitTaskContinuation

调用 AwaitUnsafeOnCompleted 后,Button_Click() 返回给它的调用者,即 Message Loop。然后 Message Loop 调用 GetMessage(),它从 Message Queue 中获取下一条消息。下一条消息可能是用户输入,如击键或鼠标点击;或者,如果 Message Queue 中没有消息,则 GetMessage() 检查计时器是否已过期,如果是,它将创建一个 WM_TIMER 消息并返回。

计时器

这引出了一个关于使用 Message Queue 的计时器的有趣题外话。对于每一个当前设置的计时器,Windows 都会维护一个计数器值,该值在每次硬件计时器滴答时递减。当该计数器达到 0 时,Windows 会在相应应用程序的 Message Queue 头部设置一个计时器已过期标志。当 Message Loop 调用 GetMessage() 时,如果 Message Queue 为空,GetMessage() 就会检查计时器已过期标志,如果已设置,GetMessage() 会重置计时器已过期标志,并生成并返回一个 WM_TIMER 消息。

由于只有在 Message Queue 为空时才会检查计时器是否已过期,因此如果 Message Queue 中有大量消息,或者所有 CPU 都忙于执行其他事情(可能与当前正在运行的程序完全无关),则 WM_TIMER 消息可能会延迟。

甚至可能在返回 WM_TIMER 消息之前,多个计时周期就会过期。但是,只会返回一个 WM_TIMER 消息,它将代表所有过期的周期。

GetMessage() 也可能在另一个时间段过期之前返回一个延迟的 WM_TIMER 消息,然后紧接着第一个消息之后又返回第二个 WM_TIMER 消息。

摘要

这完成了我们对 await 关键字的底层工作原理的深入研究。希望本文能有所帮助。

历史

  • 2022 年 3 月 11 日:初始版本
© . All rights reserved.