前言:
用 async 关键字和 await 表达式表达的异步操作在C#5便发布了,其使用简单,让开发者能够用同步的方式来书写异步代码,真的很棒。当然,编译器在后面也做了不少工作--一个异步方法本质上是被编译器转换为一个桩方法和一个状态机。随着C#版本的不断更新,可能编译器转换后的代码有所变化,但本质的东西应该不会变太多。这篇笔记来源于C# in depth(第四版),记录一些关键的地方,便于自己记忆,希望对你也有所帮助。
文章结论主要以下五点:
(1)使用 builder 作为异步基础架构, async 方法会被转换成桩方法和状态机。 (2)状态机会追踪 builder 、方法参数、局部变量、 awaiter 以及续延中需要恢复执行的位置。 (3)编译器会创建一些代码,旨在在方法恢复时回到方法内部。 (4) INotifyCompletion和ICriticalNotifyCompletion 接口可用于控制执行上下文的贯穿。 (5) builder 方法由编译器负责调用。下面我们就上面的5点展开说明。
一、关于第一点的说明
我们编写的 async 方法会被编译器编译为一个桩方法和一个状态机。
桩方法的签名和 async 方法的签名一致。在桩方法的内部,会首先 new 一个状态机,然后初始化这个状态机,初始化的过程主要包含
①状态机的状态字段(用于记录await表达式完成后的恢复执行处)、
② builder ( AsyncTaskMethodBuilder )的初始化、
③捕获 async 方法参数后提升为字段的值。状态机初始化好之后,就要调用状态机的 builder 字段(一般情况下是 AsyncTaskMethodBuilder )的 Start 方法,之后返回 builder 字段的 Task 属性。在这个过程中要注意状态机和builder都是一个值类型,所以在builder上执行的 Start 参数有ref修饰符,表示按引用传递,原因是在 await 方法回调后值类型的状态能够保存。
至于 async 方法中的执行逻辑,则全部被转移到了状态机的 MoveNext 方法中。
桩方法和状态机之间的联系就是通过 builder 来建立的。
二、关于第二点的说明
状态机会保存 builder 的一个字段, async 方法的参数也会被提升为状态机的字段,如果在 async 方法的 await 表达式之后需要访问 async 方法中的局部变量,也需要将该局部变量保存到状态机中。状态机还保存了awaiter字段,一般情况下,不同类型的await表达式只保存一个就行。至于await之后续延中需要恢复执行的位置由一个 state 的字段来表示,当state字段的值为-1时表示为执行或正在执行,大于0时表示暂定,-2表示已经结束,结束表示正常完成或有异常。
三、关于第三点的说明
状态机中创建了大量的样板代码来“翻译” async 方法中的代码,一般我们通过反编译工具可以看到大量的 switch 、 goto 之类的语句。
//MoveNext方法的样板代码 void IAsyncStateMachine. MoveNext() { try { switch (this. state) { default: goto MethodStart; case 0: goto Label0A; case 1: goto Label1A; case 2: goto Label2A; <------ case 的数量与await表达式数量相等 } MethodStart: <------ 第一个await表达式之前的代码 <------ 设置第一个awaiter Label0A: <------ 从续延中恢复执行的代码 Label0B: <------ 快速路径和慢速路径汇合之处 <------ 剩余代码,包括更多标签以及awaiter等 } catch (Exception e) //(本行及以下5行) 通过builder填充所有异常信息 { this.state = -2; builder.SetException(e); return; } this.state = -2; //(本行及以下1行)通过builder填充方法完成的信息 builder.SetResult(); }
四、关于第四点的说明
ICriticalNotifyCompletion 可以在基础框架配合下实现执行上下文(回调)的安全访问, INotifyCompletion 需要通过 ExecutionContext 类来配合完成(ExecutionContext的Capture和Run)。这两个接口对应 builder 的AwaitOnCompleted<TAwaiter,TStateMachine>(ref TAwaiterawaiter,ref TStateMachinestateMachine)方法和AwaitUnsafeOnCompleted<TAwaiter,TStateMachine>(ref TAwaiterawaiter,ref TStateMachinestateMachine)方法,也会被后者进行调用。( builder 具体要使用哪一个方法要看Task实现了哪一个接口)
五、关于第五点的说明
builder 和状态机等等都是编译器生成的,至于调用么,当然得由编译器负责调用了。
到此这篇关于C#异步原理详情的文章就介绍到这了,更多相关C#异步原理内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!