await关键字的工作原理

目录

介绍

完整的C#反汇编程序

ildasm — C#反汇编程序

Program类

.ctor : void()

: void()

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

Display : void(string)

DoWork : void()

MoreWork : void()

d__0 → 类StateMachine

.ctor : void()

SetStateMachine

MoveNext : void()

编译程序的忠实C#表示

简化

简化Main()

简化MoveNext()

简化SubMain()

简化的C#表示

跟踪等待期间发生的事情

第1部分

第2部分

在Button_Click事件处理程序中等待

跟踪AwaitUnsafeOnCompleted

计时器

概括


介绍

本文将研究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#

完整的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——微软中间语言,或者只是IL——中间语言。这是C#编译器输出的类汇编语言。如果您不熟悉这种语言,请不要担心,我将逐行解释所有CIL代码。我们将检查CIL代码并将其转换回新的C#程序。

ildasm — C#反汇编程序

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

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

以上是类和方法的列表。方法的一般格式是:Name : ReturnType(paramters)。凭借远见卓识,我将把ildasm.exe给出的一些神秘名称更改为对用户更友好和更有意义的名称:

ildasm名称:

 

重命名为:

<Main>d__0

Class StateMachine

<Main>

static void Main()

Main

private static Task SubMain()

<Main>d__0C#编译器为我们创建的类的名称。我将该类重命名为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的构造函数开始。

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

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

<Main> : void()

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

为我们在IL DASM中双击<Main> : void()

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

static void Main()
{
}

一开始,我们看到.locals它在其中为类型TaskAwaiter定义V_0

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。我们最终将完成一些任务,因此为了帮助他们保持直截了当,我将有远见,将此处返回的任务命名为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();
}

(请注意,内部statemachineTaskAwaiter存储statemachineTask在名为m_taskprivate字段中。因此,TaskAwaiter可以访问它正在执行的await操作的Task。)

第三行:

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;
}

第四行,ldloca.s V_0加载局部变量的地址V_0。将此与第5行结合:

             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 : [mscorlib]System.Threading.Tasks.Task()

让我们继续讨论另一种Main CIL方法:

IL DASM中双击Main : class [mscorlib]System.Threading.Tasks.Task()给我们:

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

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

private static Task SubMain()
{
}

.locals为类型 <Main>d__0定义局部变量V_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_0statemachine

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)

为我们在IL DASM中双击Display : void(string)

这转化为:

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将是所有实例方法隐式隐士作为arg 0'this'指针。(您还将instance当它是实例方法而不是方法时,请参见顶部附近的 CIL 代码中的单词static。))

DoWork : void()

为我们在IL DASM中双击DoWork : void()

这转化为:

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

MoreWork : void()

为我们在IL DASM中双击MoreWork : void()

这与上面的DoWork()类似,并转换为:

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

<Main>d__0 → StateMachine

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

再次以远见卓识,我将这里的一些名称更改为更有意义的名称:

ildasm名称:

 

重命名为:

<Main>d__0

Class StateMachine

<>1__state

State

<>t__builder

Builder

<>u__1

DelayTaskAwaiter(因为这是从delayTaskTask.Delay(2000)从返回的)创建的)

我们看到这个类的大致轮廓是:

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构造函数:

代码是:

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

SetStateMachine

暂时跳过该MoveNext()方法,让我们看一下SetStateMachine代码:

我们可以看到这段代码完全没有做任何事情。唯一的指令是IL_0000: ret,它是一个return语句。这个方法在这里的唯一原因是因为这个类实现了需要它的IAsyncStateMachine接口。这转化为:

public void SetStateMachine(IAsyncStateMachine stateMachine)
{
    return;
}

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

MoveNext : void()

我们现在深入研究整个程序中最复杂的方法,MoveNext()方法。这是一个很长的问题,所以请耐心等待,我们会度过难关的。

此方法的CIL代码为:

这是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_2,V_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.com中的Task.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.PulseAll(m_lock)之前,调用Monitor.Wait(m_lock)不会返回。

首先,我们将延续动作设置为运行 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转换到完成状态时,它会运行调用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;
    }
}

检查运行第1部分的MoveNext() switch语句case default:

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任务完成时要运行的延续。(回想一下,delayTaskAwaiterdelayTask保存在一个名为m_taskprivate字段中。)完成时运行的延续delayTaskstatemachine.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操作。
  • this.Builder.SetException(ex);
    如果在运行状态机的第1部分或第2部分时抛出异常,则会调用此方法。这会将异常ex添加到异常statemachineTask.m_exceptionsHolder列表,将statemachineTask状态设置为"Faulted",然后调用statemachineTask.continuationObject操作。

我将从Builder中提取statemachineTask并将其放在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()");
        }
    }
}

虽然这个程序不会编译,因为它直接引用了一些private字段,就好像它们是public一样,我们仍然可以跟踪如果上面的程序运行会发生什么。这将使我们深入了解await关键字的工作原理。

跟踪等待期间发生的事情

按照上面的代码,我们现在可以跟踪程序的流程:

1部分

  • Main()调用SubMain()
    • SubMain()
      1. 创建statemachine
      2. 初始化statemachineState = -1
      3. 调用statemachine.MoveNext()
        1. MoveNext()看到this.State == -1并跳转到case default:它运行第1部分:
          1. DoWork()被调用
          2. Task.Delay(2000)被调用
            • Task.Delay(2000)设置一个计时器在2秒后到期并返回delayTask
          3. this.State设置为0(因此第2部分将在下次调用MoveNext()时运行)
          4. delayTask.continuationObject被设定为this.MoveNext()
          5. 返回SubMain()
      4. SubMain()返回statemachine.statemachineTask_Main()
  • 在这一点上,我们都准备好了。我们现在要做的就是坐下来,从这里开始什么都不做。

当计时器过期时,我们程序的其余部分将由delayTask处理。delayTask的继续操作设置为运行 MoveNext(),它将运行我们代码的第2部分。

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

  • Main()等待stateMachine完成:
    1. 设置statemachineTask.continuationObjectMonitor.PulseAll(m_lock)statemachineTask转换到完成状态时运行。
    2. 调用Monitor.Wait(m_lock)
      这将Main线程置于等待状态。Main线程状态更改为SleepJoinWait,线程从运行队列中移除,线程在该Main队列中接收CPU时间,并置于等待队列中。该线程不再接收CPU时间,因为它不再在运行队列中。

2部分

  • 一段时间后,2秒计时器到期,一个ThreadPool线程运行为处理计时器到期而设置的代码。这段代码调用了delayTask.continuationObject动作,它是MoveNext()(这是在第1部分调用Task.Delay(2000)delayTask返回给它之后MoveNext()设置的)。

    (如果这是一个GUI程序,而不是直接调用MoveNext(),延续动作会稍微复杂一些,首先检查我们是否需要在GUI线程上运行延续动作,如果需要,它将排队MoveNext()消息队列所以MoveNext()将在GUI线程上运行。)
    1. MoveNext()检查this.State,看到this.State == 0,然后跳转到case 0:它运行第2部分的地方。
      1. MoreWork()被调用。
      2. Console.Write("Press Enter: ")被调用。
      3. Console.ReadLine()被调用。
        1. 线程等待用户按下Enter键。
        2. 用户按下Enter键。
        3. Console.ReadLine()返回。
      4. break;

我们现在已经完成了第1部分和第2部分的运行。现在剩下要做的就是清理。

    1. statemachineTask状态设置为"RanToCompletion"
    2. 调用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()调用它的位置。
    3. 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事件处理程序中等待

现在让我们看看在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被忽略,线程返回到消息循环,继续处理其他消息。

其他一切都和以前一样。当我们从Button_Click()中返回时,delayTask将其continuationObject设置为运行 MoveNext()statemachine.State == 0,因此将其设置为运行statemachine(第 2 部分)的下一部分。

还有一个额外的区别:continuationObject现在是一个特殊的类,它同时包含持续ActionSynchronizationContext,如果我们一开始就在GUI线程上并且需要回到它,这就是让我们回到GUI线程的原因。如果我们不需要跳回GUI头,那么直接调用MoveNext();如果我们确实需要跳回GUI线程,则MoveNext()排队到Message Queue

跟踪AwaitUnsafeOnCompleted

以下详细信息提供给希望深入了解如何为WPF Button_Click()事件处理程序设置任务延续的任何人。

该行this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine);创建了一个SynchronizationContextAwaitTaskContinuation类实例并将其存储为task.continuationObject。执行这个task.continuationObjectTask类中的代码检查存储的对象是否是SynchronizationContextAwaitTaskContinuation的派生对象TaskContinuation,如果是,它调用taskContinuation.Run()方法。该Run()方法负责在正确的线程上运行继续操作。继续操作也是statemachine.MoveNext()

跟踪对AwaitUnsafeOnCompleted的调用,它传递delayTaskAwaiterawaiter,并调用在本地字段m_task中保持delayTaskdelayTaskAwaiter调用,内部调用是:

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任务延续,它被设置为SynchronizationContextAwaitTaskContinuation

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

计时器

这带来了一个关于使用Message Queue的计时器的有趣的旁注。对于当前设置的每个计时器,Windows维护一个计数器值,它会在每个硬件计时器滴答时递减。当此计数器达到0时,Windows会在相应应用程序的消息队列标头中设置计时器过期标志。当Message Loop调用GetMessage()时,如果Message Queue为空,GetMessage()则检查定时器过期标志,如果设置,则GetMessage()重置定时器过期标志,并生成并返回WM_TIMER消息。

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

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

也有可能在另一个时间段到期之前GetMessage()返回一条延迟WM_TIMER消息,然后在第一条消息之后几乎立即返回第二条WM_TIMER消息。

概括

这完成了我们深入了解await关键字发生的情况的深入研究。我希望这篇文章对您有所帮助。

https://www.codeproject.com/Articles/5327239/Internals-of-How-the-await-Keyword-Works

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值