目录
: void()Main : 类[mscorlib]System.Threading.Tasks.Task()
d__0 → 类StateMachine介绍
本文将研究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#反汇编程序
我将使用Microsoft的ildasm.exe(中间语言反汇编程序)来检查C#编译器创建的可执行文件。(ildasm.exe可能已经在您的计算机上的某个地方。在C:\Program Files (x86)\Microsoft SDKs\Windows\...下搜索...还有其他可以反汇编C#可执行文件的程序,例如ILSpy、dotPeek和. 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__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的构造函数开始。
双击上面IL DASM窗口中的.ctor : void()行会打开一个新窗口,显示CIL代码:
我们可以看到这个构造函数只是默认构造函数,除了为System.Object调用构造函数之外什么都不做,所以我们将把它从C#代码中删除,让C#编译器为我们生成这个默认构造函数。
<Main> : void()
现在让我们看一下方法<Main> : void()的代码。
为我们在IL DASM中双击<Main> : void():
在顶部,我们看到这是一个返回void的static方法。.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_task的private字段中。因此,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()给我们:
在顶部,我们看到这是一个返回Task的private 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_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)
为我们在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(因为这是从delayTask(Task.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_0, V_1, V_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_task的private字段中的任务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任务完成时要运行的延续。(回想一下,delayTaskAwaiter将delayTask保存在一个名为m_task的private字段中。)完成时运行的延续delayTask是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操作。 - 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()
- 创建statemachine
- 初始化statemachine为State = -1
- 调用statemachine.MoveNext()
- MoveNext()看到this.State == -1并跳转到case default:它运行第1部分:
- DoWork()被调用
- Task.Delay(2000)被调用
- Task.Delay(2000)设置一个计时器在2秒后到期并返回delayTask。
- this.State设置为0(因此第2部分将在下次调用MoveNext()时运行)
- delayTask.continuationObject被设定为this.MoveNext()
- 返回SubMain()
- MoveNext()看到this.State == -1并跳转到case default:它运行第1部分:
- SubMain()返回statemachine.statemachineTask_Main()
- SubMain()
- 在这一点上,我们都准备好了。我们现在要做的就是坐下来,从这里开始什么都不做。
当计时器过期时,我们程序的其余部分将由delayTask处理。delayTask的继续操作设置为运行 MoveNext(),它将运行我们代码的第2部分。
如果这是一个GUI程序,而Main()是一个代替Button_Click()的事件处理程序,那么我们将忽略返回statemachineTask并返回到Message Loop,允许Message Loop处理Message Queue中的其他消息。由于我为此演示选择了一个控制台程序,因此此时Main()不会返回,因为程序将结束。相反,Main()首先等待返回statemachineTask完成。
- Main()等待stateMachine完成:
- 设置statemachineTask.continuationObject为Monitor.PulseAll(m_lock),在statemachineTask转换到完成状态时运行。
- 调用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线程上运行。)- MoveNext()检查this.State,看到this.State == 0,然后跳转到case 0:它运行第2部分的地方。
- MoreWork()被调用。
- Console.Write("Press Enter: ")被调用。
- Console.ReadLine()被调用。
- 线程等待用户按下Enter键。
- 用户按下Enter键。
- Console.ReadLine()返回。
- break;
- MoveNext()检查this.State,看到this.State == 0,然后跳转到case 0:它运行第2部分的地方。
我们现在已经完成了第1部分和第2部分的运行。现在剩下要做的就是清理。
-
- statemachineTask状态设置为"RanToCompletion"。
- 调用statemachineTask.continuationAction,即Monitor.PulseAll()(这是之前Main()在SubMain()返回statemachineTask给它时设置的)。
- Monitor.PulseAll(m_lock) 通过 Monitor.Wait(m_lock)获取被放置在边缘的线程,将其状态从"WaitSleepJoin"更改回"Running",并将线程返回到"正在运行" 队列中,它将再次开始接收CPU时间片。
- Monitor.PulseAll(m_lock)返回到MoveNext()调用它的位置。
- 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行控制台程序。这里有一个区别,我们有两个参数传递给我们:sender和e。这两个参数恰好在我们的示例代码中都没有使用;但是,如果我们想使用它们,我们可以使用它们。
编译这段代码,然后使用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中设置了另外三个字段:
- this
- sender
- e
另一个区别是方法返回void而不是像控制台版本那样返回statemachine.Builder.Task。在这种情况下,该任务statemachine.Builder.Task被忽略,线程返回到消息循环,继续处理其他消息。
其他一切都和以前一样。当我们从Button_Click()中返回时,delayTask将其continuationObject设置为运行 MoveNext()和statemachine.State == 0,因此将其设置为运行statemachine(第 2 部分)的下一部分。
还有一个额外的区别:continuationObject现在是一个特殊的类,它同时包含持续Action和SynchronizationContext,如果我们一开始就在GUI线程上并且需要回到它,这就是让我们回到GUI线程的原因。如果我们不需要跳回GUI头,那么直接调用MoveNext();如果我们确实需要跳回GUI线程,则MoveNext()排队到Message Queue。
跟踪AwaitUnsafeOnCompleted
以下详细信息提供给希望深入了解如何为WPF Button_Click()事件处理程序设置任务延续的任何人。
该行this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine);创建了一个SynchronizationContextAwaitTaskContinuation类实例并将其存储为task.continuationObject。执行这个task.continuationObject的Task类中的代码检查存储的对象是否是SynchronizationContextAwaitTaskContinuation的派生对象TaskContinuation,如果是,它调用taskContinuation.Run()方法。该Run()方法负责在正确的线程上运行继续操作。继续操作也是statemachine.MoveNext()。
跟踪对AwaitUnsafeOnCompleted的调用,它传递delayTaskAwaiter为awaiter,并调用在本地字段m_task中保持delayTask的delayTaskAwaiter调用,内部调用是:
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