await 关键字工作原理详解





5.00/5 (30投票s)
await 关键字的内部工作原理
引言
本文将深入探讨 await
关键字的工作原理。我们将通过分析 C# 编译器在编译以下五行代码程序时生成的代码来做到这一点。
public static async Task Main()
{
DoWork(); // ┐ Part 1
await Task.Delay(2000); // ┘
MoreWork(); // ┐ Part 2
Console.Write("Press Enter: "); // │
Console.ReadLine(); // ┘
}
该程序通过调用 DoWork()
执行一些工作,然后执行 await
,最后通过调用 MoreWork()
执行更多工作。程序最后等待用户按下 Enter 键。
我们将分析 C# 编译器编译此程序时生成的代码,然后将编译后的代码翻译回 C#。
目录
- 完整的 C# 程序反编译
- ildasm — C# 反编译器
- 类 Program
- 编译后程序的忠实 C# 表示
- 简化
- 简化的 C# 表示
- 追踪 await 期间发生的情况
- 在 Button_Click 事件处理程序中使用 await
- 计时器
- 摘要
- 相关文章
- 历史
完整的 C# 程序反编译
这是我们将要测试的程序的完整 C# 代码。
namespace ConsoleAppTCSa
{
class Program
{
public static async Task Main()
{
DoWork(); // ┐ Part 1
await Task.Delay(2000); // ┘
MoreWork(); // ┐ Part 2
Console.Write("Press Enter: "); // │
Console.ReadLine(); // ┘
}
private static void DoWork()
{
Display("Enter DoWork()");
Thread.Sleep(2000);
Display("Exit DoWork()");
}
private static void MoreWork()
{
Display("Enter MoreWork()");
Thread.Sleep(2000);
Display("Exit MoreWork()");
}
private static void Display(string s)
{
Console.WriteLine("{0:hh\\:mm\\:ss\\:fff} {1}", DateTime.Now, s);
}
}
}
我们将把这个 C# 程序编译成一个 *.exe 文件,然后反编译这个 *.exe 文件,以查看 C# 编译器生成的 CIL 代码。(对于使用 .NET Core 而不是 .NET Framework 编译的程序,请反编译 *.dll 文件。)
CIL 是“通用中间语言”,有时也称为 MSIL – Microsoft 中间语言,或简称为 IL – 中间语言。这是 C# 编译器输出的类似汇编的语言。如果你不熟悉这种语言,请不用担心,我将逐行解释所有的 CIL 代码。我们将检查 CIL 代码并将其翻译回一个新的 C# 程序。
ildasm — C# 反编译器
我将使用微软的 ildasm.exe(中间语言反编译器)来检查 C# 编译器创建的可执行文件。(ildasm.exe 可能已经在你的电脑上某个地方了。请在 C:\Program Files (x86)\Microsoft SDKs\Windows\... 下搜索。还有其他程序可以反编译 C# 可执行文件,例如 ILSpy、dotPeek 和 .NET Reflector。)
这是当我们加载上面程序的已编译可执行文件时 ildasm.exe 显示的内容。我在右侧添加了一些 C# 注释。
上面是类和方法的列表。方法的通用格式是:名称 : 返回类型(参数)
。我将有远见地将 ildasm.exe 给出的一些晦涩难懂的名称更改为更友好、更有意义的名称。
ildasm 名称 | 重命名为 | |
<Main>d__0 | → | 类 StateMachine |
<Main> | → | static void Main() |
Main | → | private static Task SubMain() |
<Main>d__0
是 C# 编译器为我们创建的一个类名称。我将把这个类重命名为 StateMachine
。
另外,我们有两个名称相似的方法:
<Main>
(尖括号是方法名的一部分)Main
当我们检查这两个方法的详细信息时,我们会发现方法 <Main>
是程序的入口点,而方法 Main
才是我们上面示例 C# 程序方法 Main()
中五行代码实际去向的地方。
类 Program
现在,我们可以根据 IL DASM 反编译器的信息开始构建我们已编译程序的 C# 大纲了。
namespace ConsoleAppTCSa
{
class Program
{
class StateMachine // <Main>d__0
{
}
public Program() // .ctor : void() // the constructor
// (which does nothing special
// so we'll skip this)
{
}
static void Main() // <Main> : void() // the main entrypoint
{
}
private static void Display(string s) // Display : void(string)
{
}
private static void DoWork() // DoWork : void()
{
}
private static Task SubMain() // Main : class[mscorlib]System.Threading.Tasks.Task()
{
}
private static void MoreWork() // MoreWork : void()
{
}
}
}
让我们逐一查看这些方法。
.ctor : void()
让我们从查看 Program
类的构造函数开始。
在上面的 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
,它定义了 V_0
为 TaskAwaiter
类型。
static void Main()
{
TaskAwaiter V_0;
}
分解第一行 CIL 代码,我们有:
command +-------------- return type --------------+ +---- called method ------+
↓ │ │ │ │
IL_0000: call class [mscorlib]System.Threading.Tasks.Task ConsoleAppTCSa.Program.Main
我将这个方法调用的名称重命名为 SubMain()
,因为我们已经有一个 Main()
方法了。因此,第一行调用我正在重命名的 SubMain()
并返回一个 Task
。我们最终会有几个 Task,为了便于区分,我将有远见地将这里返回的 Task 命名为 statemachineTask
。
static void Main()
{
TaskAwaiter V_0;
Task statemachineTask = SubMain();
}
第二行获取该调用的结果,即 statemachineTask
,并调用 statemachineTask.GetAwaiter()
。我们看到此调用的返回类型是 TaskAwaiter
。
instance
关键字表示这是一个类实例而不是一个 static
方法。callvirt
类似于 call
,但有一个区别是它在使用类实例之前还会检查其有效性。由于返回的 TaskAwaiter
来自 statemachineTask
,我将其命名为 statemachineTaskAwaiter
。
static void Main()
{
TaskAwaiter V_0;
Task statemachineTask = SubMain();
TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
}
(请注意,在内部,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;
}
第 4 行 ldloca.s V_0
是“加载局部变量 V_0
的地址”。结合第五行
not return
command static type +-------------------------- called method -----------------------+
↓ ↓ ↓ │ │
call instance void [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::GetResult()
翻译为调用 V_0.GetResult();
。
static void Main()
{
TaskAwaiter V_0;
Task statemachineTask = SubMain();
TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
V_0 = statemachineTaskAwaiter;
V_0.GetResult();
}
最后一行 ret
当然是 return
。
static void Main()
{
TaskAwaiter V_0;
Task statemachineTask = SubMain();
TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
V_0 = statemachineTaskAwaiter;
V_0.GetResult();
return;
}
消除不必要的局部变量 V_0
得到:
static void Main()
{
Task statemachineTask = SubMain();
TaskAwaiter statemachineTaskAwaiter = statemachineTask.GetAwaiter();
statemachineTaskAwaiter.GetResult();
return;
}
Main : class [mscorlib]System.Threading.Tasks.Task()
让我们继续处理另一个 Main
CIL 方法。
在 IL DASM 中双击 Main : class [mscorlib]System.Threading.Tasks.Task()
会得到:
在顶部,我们看到这是一个返回 Task
的 private static
方法。
我将这个 CIL Main
方法重命名为 SubMain()
,因为我们已经有一个 Main()
方法了。
private static Task SubMain()
{
}
行 .locals
定义了局部变量 V_0
为 <Main>d__0
类型,这是一个类名。我很有远见地将这个类重命名为 StateMachine
。
private static Task SubMain()
{
StateMachine V_0;
}
将此方法其余部分从 CIL 翻译为 C# 得到:
private static Task SubMain()
{
// .locals init ([0] class ConsoleAppAsync0.Program/'<Main>d__0' V_0)
StateMachine V_0;
// IL_0000: newobj instance void
// ConsoleAppAsync0.Program/'<Main>d__0'::.ctor() ? new StateMachine()
// IL_0005: stloc.0 ? store result in V_0
V_0 = new StateMachine();
// IL_0007: call
// valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
// [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
// IL_000c: stfld
// valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
// ConsoleAppAsync0.Program/'<Main>d__0'::'<>t__builder'
V_0.Builder = new AsyncTaskMethodBuilder();
// IL_0011: ldloc.0 ? load local V_0
// IL_0012: ldc.i4.m1 ? load -1
// IL_0013: stfld int32 ConsoleAppAsync0.Program/'<Main>d__0'::'<>1__state' ?
// store result in field V_0.State
V_0.State = -1;
// IL_0019: ldflda
// valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
// ConsoleAppAsync0.Program/'<Main>d__0'::'<>t__builder' ?
// load address of field: V_0.t__builder
// IL_001e: ldloca.s V_0 ? load address of local V_0 (V_0 will be argument passed by ref)
// IL_0020: call instance void
// [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start
// <class ConsoleAppAsync0.Program/'<Main>d__0'>(!!0&)
V_0.Builder.Start(ref V_0);
// IL_0025: ldloc.0 ? load V_0
// IL_0026: ldflda valuetype
// [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
// ConsoleAppAsync0.Program/'<Main>d__0'::'<>t__builder' ?
// load address of field: V_0.t__builder
// IL_002b: call instance class [mscorlib]System.Threading.Tasks.Task
// [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::
// get_Task() ? V_0.t__builder.Task;
// IL_0030: ret ? return V_0.t__builder.Task;
return V_0.Builder.Task;
}
删除 CIL 代码注释后,我们得到:
private static Task SubMain()
{
StateMachine V_0;
V_0 = new StateMachine();
V_0.Builder = new AsyncTaskMethodBuilder();
V_0.State = -1;
V_0.Builder.Start(V_0);
return V_0.Builder.Task;
}
将 V_0
重命名为 statemachine
得到:
private static Task SubMain()
{
StateMachine statemachine = new StateMachine();
statemachine.Builder = new AsyncTaskMethodBuilder();
statemachine.State = -1;
statemachine.Builder.Start(ref statemachine);
return statemachine.Builder.Task;
}
Display : void(string)
在 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
将是所有实例方法隐式拥有的“this
”指针,作为 arg 0
。(您还会在 CIL 代码的顶部附近看到 instance
一词,表示它是实例方法而不是 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 → class StateMachine
让我们打开 <Main>d__0
类,看看里面有什么。
再次,我很有远见地将这里的一些名称更改为更有意义的名称。
ildasm 名称 | 重命名为 | |
<Main>d__0 | → | 类 StateMachine |
<>1__state | → | 状态 |
<>t__builder | → | Builder |
<>u__1 | → | DelayTaskAwaiter (因为它是由 Task.Delay(2000) 返回的 delayTask 创建的) |
我们看到这个类的通用大纲是:
class StateMachine : IAsyncStateMachine
{
public int State; // 1__state
public AsyncTaskMethodBuilder Builder; // t__builder
private TaskAwaiter DelayTaskAwaiter; // u__1;
public StateMachine() // .ctor : void() // the constructor
// (which does nothing special so we'll skip this)
{
}
public void MoveNext()
{
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}
.ctor : void()
查看 Class StateMachine
的构造函数。
代码是:
我们看到这个构造函数只是默认构造函数,它除了调用 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.Wait(m_lock)
直到另一个线程调用 Monitor.PulseAll(m_lock)
才会返回。
首先,我们将 continuation action 设置为运行 Monitor.PulseAll(m_lock)
,然后调用 Monitor.Wait(m_lock)
并等待。
static void Main() // <Main> : void // the main entrypoint
{
private volatile object m_lock = new Object();
Task statemachineTask = SubMain();
statemachineTask.Wait();
statemachineTask.continuationObject = new Action(() =>
lock (m_lock)
{
Monitor.PulseAll(m_lock));
});
lock (m_lock)
{
Monitor.Wait(m_lock);
}
if (!statemachineTask.IsRanToCompletion) ThrowForNonSuccess(statemachineTask);
return;
}
当 statemachineTask
转换为已完成状态时,它会运行 continuation action,该 action 调用 Monitor.PulseAll(m_lock)
,从而使我们从等待中释放出来。
简化 MoveNext()
继续处理 StateMachine
类的方法 MoveNext()
,调用 if (delayTaskAwaiter.IsCompleted) goto IsCompleted;
只是一个优化,以跳过等待 delayTask
完成(如果它已经完成)。让我们移除此优化和不必要的优化局部变量。
class StateMachine : IAsyncStateMachine // <Main>d__0
{
public int State; // 1__state
public AsyncTaskMethodBuilder Builder; // t__builder
private TaskAwaiter DelayTaskAwaiter; // u__1;
public void MoveNext()
{
// Local variables
Int32 state; // V_0
TaskAwaiter delayTaskAwaiter; // V_1
state = this.State;
try
{
switch (this.State)
{
default: // case -1
DoWork(); // ┐ Part 1
Task delayTask = Task.Delay(2000); // │
TaskAwaiter delayTaskAwaiter = delayTask.GetAwaiter(); // │
if (delayTaskAwaiter.IsCompleted) goto IsCompleted; // │
state = 0; // │
this.State = 0; // │
this.DelayTaskAwaiter = delayTaskAwaiter; // │
StateMachine statemachine = this; // │
this.Builder.AwaitUnsafeOnCompleted // │
(ref delayTaskAwaiter, ref statemachine); // │
return; // ┘
case 0:
delayTaskAwaiter = this.DelayTaskAwaiter; // ┐ Part 2
this.DelayTaskAwaiter = default; // │
state = -1; // │
this.State = -1; // unnecessary // │
IsCompleted: this.DelayTaskAwaiter.GetResult(); // │
MoreWork(); // │
Console.Write("Press Enter: "); // │
Console.ReadLine(); // ┘
break;
}
}
catch (Exception ex)
{
this.State = -1; // unnecessary
this.Builder.SetException(ex);
return;
}
this.State = -2; // unnecessary
this.Builder.SetResult();
return;
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
return;
}
}
这留下了:
class StateMachine : IAsyncStateMachine // <Main>d__0
{
public int State; // 1__state
public AsyncTaskMethodBuilder Builder; // t__builder
public void MoveNext()
{
try
{
switch (this.State)
{
default: // case -1
DoWork(); // ┐ Part 1
Task delayTask = Task.Delay(2000); // │
TaskAwaiter delayTaskAwaiter = delayTask.GetAwaiter(); // │
this.State = 0; // │
StateMachine statemachine = this; // │
this.Builder.AwaitUnsafeOnCompleted // │
(ref delayTaskAwaiter, ref statemachine); // │
return; // ┘
case 0:
MoreWork(); // ┐ Part 2
Console.Write("Press Enter: "); // │
Console.ReadLine(); // ┘
break;
}
}
catch (Exception ex)
{
this.Builder.SetException(ex);
return;
}
this.Builder.SetResult();
return;
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
return;
}
}
检查 MoveNext() switch
语句的 default:
情况,它运行 Part 1。
default: // case -1
DoWork(); // ┐ Part 1
Task delayTask = Task.Delay(2000); // │
TaskAwaiter delayTaskAwaiter = delayTask.GetAwaiter(); // │
this.State = 0; // │
StateMachine statemachine = this; // │
this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine);// │
return; // ┘
行:
this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine);
设置在 delayTask
完成时运行的 continuation。(回想一下,delayTaskAwaiter
在名为 m_task
的 private
字段中持有 delayTask
。)当 delayTask
完成时运行的 continuation 是 statemachine.MoveNext()
。我们可以概念上将此行替换为:
delayTask.continuationObject = new Action(() => this.MoveNext());
default: // case -1
DoWork(); // ┐ Part 1
Task delayTask = Task.Delay(2000); // │
TaskAwaiter delayTaskAwaiter = delayTask.GetAwaiter(); // │
this.State = 0; // │
StateMachine statemachine = this; // │
this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine);// │
delayTask.continuationObject = new Action(() => this.MoveNext()); // │
return; // ┘
简化 SubMain()
在 SubMain()
中,行 statemachine.Builder.Start(ref statemachine)
基本上调用 statemachine.MoveNext()
。
private static Task SubMain() // Main : class [mscorlib]System.Threading.Tasks.Task()
{
StateMachine statemachine = new StateMachine();
statemachine.Builder = new AsyncTaskMethodBuilder();
statemachine.State = -1;
statemachine.Builder.Start(ref statemachine);
statemachine.MoveNext()
return statemachine.Builder.Task;
}
行 return statemachine.Builder.Task;
返回一个由 Builder
创建的新任务。我将其称为 statemachineTask
。此任务实际上不运行任何内容,它仅用作信号,指示状态机何时完成,以及作为存储结果值(如果存在)的地方。此任务在 MoveNext()
的两个地方被操作。
this.Builder.SetResult();
这会将statemachineTask
的状态设置为 "RanToCompletion
" 并调用statemachineTask.continuationObject
action。this.Builder.SetException(ex);
当状态机运行 Part 1 或 Part 2 时抛出异常时,将调用此方法。它会将异常ex
添加到statemachineTask.m_exceptionsHolder
(这是一个异常列表),将statemachineTask
的状态设置为 "Faulted
",然后调用statemachineTask.continuationObject
action。
我将 statemachineTask
从 Builder
中提取出来并放入 MoveNext()
。然后,我们将不再需要 Builder
。
private static Task SubMain() // Main : class [mscorlib]System.Threading.Tasks.Task()
{
StateMachine statemachine = new StateMachine();
statemachine.Builder = new AsyncTaskMethodBuilder();
statemachine.State = -1;
statemachine.MoveNext();
return statemachine.Builder.Task;
return statemachine.statemachineTask;
}
class StateMachine : IAsyncStateMachine // <Main>d__0
{
public int State; // 1__state
public AsyncTaskMethodBuilder Builder; // t__builder
public Task statemachineTask = new Task(); // was Builder.Task
public void MoveNext()
{
try
{
switch (this.State)
{
default: // case -1
DoWork(); // ┐ Part 1
Task delayTask = Task.Delay(2000); // │
this.State = 0; // │
delayTask.continuationObject = // │
new Action(() => this.MoveNext()); // │
return; // ┘
case 0:
MoreWork(); // ┐ Part 2
Console.Write("Press Enter: "); // │
Console.ReadLine(); // ┘
break;
}
}
catch (Exception ex)
{
this.Builder.SetException(ex);
this.statemachineTask.m_stateFlags = m_stateFlags |
TASK_STATE_FAULTED; // set task state to "Faulted"
Action action = this.statemachineTask.continuationObject as Action;
action();
return;
}
this.Builder.SetResult();
this.statemachineTask.m_stateFlags = m_stateFlags |
TASK_STATE_RAN_TO_COMPLETION; // set task state to "RanToCompletion"
Action action = this.statemachineTask.continuationObject as Action;
action();
return;
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
return;
}
}
简化的 C# 表示
将所有这些整合在一起,我们得到了程序的完整简化版本,它更好地展示了 await
语句在底层发生了什么。
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleAppTCSa
{
class Program
{
static void Main() // <Main> : void // the main entrypoint
{
private volatile object m_lock = new Object();
Task statemachineTask = SubMain();
statemachineTask.continuationObject = new Action(() =>
lock (m_lock)
{
Monitor.PulseAll(m_lock));
});
lock (m_lock)
{
Monitor.Wait(m_lock);
}
if (!statemachineTask.IsRanToCompletion) ThrowForNonSuccess(statemachineTask);
return;
}
private static Task SubMain() // Main : class
// [mscorlib]System.Threading.Tasks.Task()
{
StateMachine statemachine = new StateMachine();
statemachine.State = -1;
statemachine.MoveNext();
return statemachine.statemachineTask;
}
class StateMachine : IAsyncStateMachine // <Main>d__0
{
public int State; // 1__state
public Task statemachineTask = new Task(); // was Builder.Task
public void MoveNext()
{
try
{
switch (this.State)
{
default: // case -1
DoWork(); // ┐ Part 1
Task delayTask = Task.Delay(2000); // │
this.State = 0; // │
delayTask.continuationObject = // │
new Action(() => this.MoveNext()); // │
return; // ┘
case 0:
MoreWork(); // ┐ Part 2
Console.Write("Press Enter: "); // │
Console.ReadLine(); // ┘
break;
}
}
catch (Exception ex)
{
this.statemachineTask.m_stateFlags = m_stateFlags |
TASK_STATE_FAULTED; // set task state to "Faulted"
Action action = this.statemachineTask.continuationObject as Action;
action();
return;
}
this.statemachineTask.m_stateFlags = m_stateFlags |
TASK_STATE_RAN_TO_COMPLETION; // set task state to "RanToCompletion"
Action action = this.statemachineTask.continuationObject as Action;
action();
return;
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
return;
}
}
private static void Display(string s) // Display : void()
{
Console.WriteLine("{0:hh\\:mm\\:ss\\:fff} {1}", DateTime.Now, s);
}
private static void DoWork() // DoWork : void()
{
Display("Enter DoWork()");
Thread.Sleep(2000);
Display("Exit DoWork()");
}
private static void MoreWork() // MoreWork : void()
{
Display("Enter MoreWork()");
Thread.Sleep(2000);
Display("Exit MoreWork()");
}
}
}
虽然此程序无法编译,因为它直接像访问 public
字段一样引用了一些 private
字段,但我们仍然可以追踪上述程序如果运行会发生什么。这将为我们提供深入了解 await
关键字工作原理的洞察。
追踪 await 期间发生的情况
遵循上面的代码,我们现在可以追踪程序的流程。
第一部分
Main()
调用SubMain()
。SubMain()
- 创建
statemachine
。 - 将
statemachine
初始化为State = -1
。 - 调用
statemachine.MoveNext()
。MoveNext()
看到this.State == -1
并跳转到 casedefault:
,在那里运行 Part 1。- 调用
DoWork()
。 - 调用
Task.Delay(2000)
。Task.Delay(2000)
设置一个 2 秒后过期的计时器并返回delayTask
。
this.State
被设置为0
(因此下次调用MoveNext()
时将运行 Part 2)。delayTask.continuationObject
被设置为this.MoveNext()
。- 返回到
SubMain()
。
- 调用
SubMain()
将statemachine.statemachineTask
返回给Main()
。
- 创建
- 此时,我们已全部设置好。接下来我们所要做的就是放松,什么也不做。
“你并不总是需要完成很多事情。有时,停止工作,放松下来,什么都不做,是完全可以的,也是绝对必要的。” — Lori Deschene, Tiny Buddha 网站
程序的其余部分将由 delayTask
在计时器到期时处理。delayTask
的 continuation action 被设置为运行 MoveNext()
,它将运行我们代码的 Part 2。
如果这是一个 GUI 程序,并且 Main()
实际上是一个 Button_Click()
事件处理程序,那么我们将忽略返回的 statemachineTask
,直接返回到 Message Loop,让 Message Loop 处理 Message Queue 中的其他消息。由于我选择了控制台程序作为本次演示,Main()
在此时不会返回,因为程序将结束。相反,Main()
首先等待返回的 statemachineTask
完成。
Main()
等待stateMachine
完成。- 将
statemachineTask.continuationObject
设置为在statemachineTask
转换为完成状态时运行Monitor.PulseAll(m_lock)
。 - 调用
Monitor.Wait(m_lock)
。
这会将Main
线程置于等待状态。Main
线程的状态变为 "SleepJoinWait
",并且Main
线程从接收 CPU 时间的“运行”队列中移除,放入“等待”队列。由于线程不再在运行队列中,它不会再获得 CPU 时间。
- 将
第二部分
“静静地坐着,什么也不做,春天来了,草自己就长了。” — 禅宗谚语
- 过了一会儿,2 秒计时器到期,一个
ThreadPool
线程运行了为处理计时器到期而设置的代码。这段代码调用delayTask.continuationObject
action,即MoveNext()
(这是MoveNext()
Part 1 调用Task.Delay(2000)
并将delayTask
返回给它后设置的)。
(如果这是一个 GUI 程序,则不是直接调用MoveNext()
,而是 continuation action 会稍微复杂一些,首先检查是否需要 GUI 线程上运行 continuation action,如果需要,它会将MoveNext()
排列到 Message Queue,以便MoveNext()
在 GUI 线程上运行。)MoveNext()
检查this.State
,发现this.State == 0
,并跳转到case 0:
,在那里运行 Part 2。- 调用
MoreWork()
。 - 调用
Console.Write("Press Enter: ")
。 - 调用
Console.ReadLine()
。- 线程等待用户按下 Enter 键。
- 用户按下 Enter 键。
Console.ReadLine()
返回。
break;
现在我们已经完成了 Part 1 和 Part 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 事件处理程序中使用 await
现在让我们看看在 WPF Button_Click()
事件处理程序方法中使用 await
时会发生什么。假设我们有一个方法看起来像这样:
private async void Button_Click(object sender, RoutedEventArgs e)
{
DoWork(); // ┐ Part 1
await Task.Delay(2000); // ┘
MoreWork(); // Part 2
}
这与我们上面开始的 5 行控制台程序类似。一个不同之处在于,这里传递给我们两个参数: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
被忽略,线程返回到 Message Loop,继续处理其他消息。
其他一切都和以前一样。当我们从 Button_Click()
返回时,delayTask
的 continuationObject
被设置为运行 MoveNext()
,并且 statemachine.State == 0
,因此它被设置为运行 statemachine
的下一部分(Part 2)。
还有一个额外的区别:continuationObject
现在是一个特殊的类,它包含 continuation Action
和 SynchronizationContext
,它帮助我们回到 GUI 线程(如果我们最初在 GUI 线程上并且需要返回)。如果我们不需要跳回到 GUI 线程,则直接调用 MoveNext()
;如果需要跳回到 GUI 线程,则将 MoveNext()
排列到 Message Queue。这如何实现,以及对 Message Queue 和 Message Loop 的解释,在我另一篇论文中有说明:Async/Await 解释(附带图表和示例):如何回到 UI 线程。
追踪 AwaitUnsafeOnCompleted
以下详细信息适用于希望深入了解 WPF Button_Click()
事件处理程序如何设置任务 continuation 的用户。
行 this.Builder.AwaitUnsafeOnCompleted(ref delayTaskAwaiter, ref statemachine);
创建 SynchronizationContextAwaitTaskContinuation
类的实例,并将其存储为 task.continuationObject
。Task
类中执行此 task.continuationObject
的代码检查存储的对象是否是 TaskContinuation
(SynchronizationContextAwaitTaskContinuation
从其派生),如果是,则调用 taskContinuation.Run()
方法。Run()
方法负责在正确线程上运行 continuation action。continuation action 仍然是 statemachine.MoveNext()
。
追踪对 AwaitUnsafeOnCompleted
的调用,该调用将 delayTaskAwaiter
作为 awaiter
传递,并回想 delayTaskAwaiter
在局部字段 m_task
中持有 delayTask
,内部调用如下:
this.Builder.AwaitUnsafeOnCompleted(ref awaiter, ref statemachine);
awaiter.UnsafeOnCompleted(continuation);
TaskAwaiter.OnCompletedInternal(m_task, continuation,
continueOnCapturedContext:true, flowExecutionContext:false);
task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext,…
if (continueOnCapturedContext)
tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, continuationAction,…
tc
是我们的 delayTask
任务 continuation,它被设置为 SynchronizationContextAwaitTaskContinuation
。
调用 AwaitUnsafeOnCompleted
后,Button_Click()
返回给它的调用者,即 Message Loop。然后 Message Loop 调用 GetMessage()
,它从 Message Queue 中获取下一条消息。下一条消息可能是用户输入,如击键或鼠标点击;或者,如果 Message Queue 中没有消息,则 GetMessage()
检查计时器是否已过期,如果是,它将创建一个 WM_TIMER
消息并返回。
计时器
这引出了一个关于使用 Message Queue 的计时器的有趣题外话。对于每一个当前设置的计时器,Windows 都会维护一个计数器值,该值在每次硬件计时器滴答时递减。当该计数器达到 0 时,Windows 会在相应应用程序的 Message Queue 头部设置一个计时器已过期标志。当 Message Loop 调用 GetMessage()
时,如果 Message Queue 为空,GetMessage()
就会检查计时器已过期标志,如果已设置,GetMessage()
会重置计时器已过期标志,并生成并返回一个 WM_TIMER
消息。
由于只有在 Message Queue 为空时才会检查计时器是否已过期,因此如果 Message Queue 中有大量消息,或者所有 CPU 都忙于执行其他事情(可能与当前正在运行的程序完全无关),则 WM_TIMER
消息可能会延迟。
甚至可能在返回 WM_TIMER
消息之前,多个计时周期就会过期。但是,只会返回一个 WM_TIMER
消息,它将代表所有过期的周期。
GetMessage()
也可能在另一个时间段过期之前返回一个延迟的 WM_TIMER
消息,然后紧接着第一个消息之后又返回第二个 WM_TIMER
消息。
摘要
这完成了我们对 await
关键字的底层工作原理的深入研究。希望本文能有所帮助。
相关文章
历史
- 2022 年 3 月 11 日:初始版本