async/awat 贴脸开大,这次你总该明白了

出来混总是要还的

最近在准备记录一个.NET Go核心能力的深度对比, 关于.NET/Go的异步实现总感觉没敲到点上。

async/await是.NET界老生常谈的话题,每至于此,状态机又是必聊的话题,但是状态机又是比较晦涩难懂的话题。

一线码农大佬\]在博客园2020年写的《[await,async 我要把它翻个底朝天,这回你总该明白了吧](https://link.juejin.cn?target=https%3A%2F%2Fwww.cnblogs.com%2Fhuangxincheng%2Fp%2F13558006.html "https://www.cnblogs.com/huangxincheng/p/13558006.html")》手把手实现了异步状态机,这篇文章很是经典, 但是评论区很多人还是在吐槽看不懂, 我也看的不是很懂。 以我浅薄的推测: 1. 一线大佬的知识体系太宽太深,有的验证点在文字之外,需要我们自己去确认。 2. 有些内容太细节,挖的太深,出不来。 3. 很多人不熟悉**状态机设计模式**, 导致看大佬文章,知其然不知其所以然。 > 我以前用Go语言演示了状态机: [我是状态机,有一颗永远骚动的机器引擎](https://link.juejin.cn?target=https%3A%2F%2Fwww.cnblogs.com%2FJulianHuang%2Fp%2F15304184.html "https://www.cnblogs.com/JulianHuang/p/15304184.html"), 当时有粉丝留言让用.NET 实现状态机, 这篇文章也算是对粉丝的喊话。 ### 状态机:一颗永远骚动的机器引擎 状态机是一种行为设计模式,它允许对象在其内部状态改变时改变其行为。看起来好像对象改变了它的类。 请仔细理解上面每一个字。 我们以自动售货机为例,为简化演示,我们假设自动售货机只有1种商品, 故自动售货机有`itemCount` 、`itemPrice` 2个属性 不考虑动作的前后相关性,自动售货机对外暴露4种行为: * 给自动售货机加货 `addItem` * 选择商品 `requestItem` * 付钱 `insertMoney` * 出货 `dispenseItem` 重点来了,当发生某种行为,自动售货机会进入如下4种状态之一, 并据此状态做出特定动作, 之后进入另外一种状态..... * 有商品 `hasItem` * 无商品 `noItem` * 已经选好商品 `itemRequested` * 已付钱 `hasMoney` ![](https://file.jishuzhan.net/article/1780052848025800705/c6d3654b82760fa28501910c1ec27637.webp) 当对象可能处于多种不同的状态之一、根据传入的动作更改当前的状态, 继续接受后续动作,状态再次发生变化..... 这样的模式类比于机器引擎,周而复始的工作和状态转化,这也是状态机的定语叫"**机Machine**"的原因。 有了以上思路,我们尝试沟通UML 伪代码 ![](https://file.jishuzhan.net/article/1780052848025800705/2085645286a0919f3a01e1882c51cf1a.webp) 状态机设计模式的伪代码实现: * 所谓的机器Machine维护了状态切换的上下文 * 机器对外暴露的行为,驱动机器的状态变更 * 机器到达特定的状态 只具备特定的行为,其他行为是不被允许的 Go版本的售货机(状态机设计模式)的源码,请参见原文[www.cnblogs.com/JulianHuang...](https://link.juejin.cn?target=https%3A%2F%2Fwww.cnblogs.com%2FJulianHuang%2Fp%2F15304184.html%25E3%2580%2582 "https://www.cnblogs.com/JulianHuang/p/15304184.html%E3%80%82") ### 对async/await贴脸开大 还是以一线码农大佬的异步下载为例: ![](https://file.jishuzhan.net/article/1780052848025800705/95d63daafb49fcf9f637ba7dce19c332.webp) 编译器词法分析定位到async/await语法糖,就会为开发者生成状态机代码。 一个新出炉的状态机包含如下属性 : ![](https://file.jishuzhan.net/article/1780052848025800705/c03eba9493abafbdc9d9296963f560d6.webp) (1) 初始化的状态机,以async所在的函数名命名,示例状态机为`d__1`; (2)车钥匙启动状态机之后,立马返回,这正是`异步编程`的内涵。 一个简单的、成功的状态机转化如图: ![](https://file.jishuzhan.net/article/1780052848025800705/11678256ed8344f32a8e84ab5225bc00.webp) #### 1. 初始状态 * state= -1; * Start状态机; 即时返回。 ```ini Program.d__1 stateMachine = new Program.d__1(); stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create(); stateMachine.<>1__state = -1; stateMachine.<>t__builder.Startd__1>(ref stateMachine); return stateMachine.<>t__builder.Task; ``` [车钥匙Start,内部实际是执行`MoveNext`方法](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FOmerMor%2FAsyncBridge%2Fblob%2Faa806b61be32363934378ffc2df979dc8b34ea88%2Fsrc%2FAsyncBridge%2FRuntime.CompilerServices%2FAsyncMethodBuilderCore.cs%23L27 "https://github.com/OmerMor/AsyncBridge/blob/aa806b61be32363934378ffc2df979dc8b34ea88/src/AsyncBridge/Runtime.CompilerServices/AsyncMethodBuilderCore.cs#L27"), 该方法会设置异步任务的`TaskAwaiter`对象, 紧接着stateMachine进入新的状态。 #### 2. 异步任务未完成状态 * 切换到state = 0 * 调用[`AwaitUnsafeOnCompleted()`向底层注册回调, 2个入参](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FOmerMor%2FAsyncBridge%2Fblob%2Faa806b61be32363934378ffc2df979dc8b34ea88%2Fsrc%2FAsyncBridge%2FRuntime.CompilerServices%2FAsyncTaskMethodBuilder.cs%23L328 "https://github.com/OmerMor/AsyncBridge/blob/aa806b61be32363934378ffc2df979dc8b34ea88/src/AsyncBridge/Runtime.CompilerServices/AsyncTaskMethodBuilder.cs#L328") * 回调参数1:异步结果`TaskAwaiter` * 回调参数2: 当前状态机 ```ini int num1 = this.<>1__state; if (num1 != 0) { this.5__1 = new WebClient(); awaiter = this.5__1.DownloadStringTaskAsync(new Uri("http://cnblogs.com")).GetAwaiter(); if (!awaiter.IsCompleted) { this.<>1__state = num2 = 0; this.<>u__1 = awaiter; Program.d__1 stateMachine = this; this.<>t__builder.AwaitUnsafeOnCompleted, Program.d__1>(ref awaiter, ref stateMachine); return; } } ``` IO数据就绪,会在IO线程执行回调方法`GetCompletionAction`,利用入参2:状态机,再次执行状态机的`MoveNext`方法, 进入新的状态 #### 3. 异步结果就绪状态 * 切换到state = -1; * taskAwaiter获取异步任务结果; * 执行后继代码; ```ini else { awaiter = this.<>u__1; this.<>u__1 = new TaskAwaiter(); this.<>1__state = num2 = -1; } this.<>s__3 = awaiter.GetResult(); this.5__2 = this.<>s__3; this.<>s__3 = (string) null; content52 = this.5__2; // 后继代码段 ``` #### 4. 状态机终止状态 * 切换到state =-2; * 设置状态机最终返回值; ```ini this.<>1__state = -2; this.5__1 = (WebClient) null; this.5__2 = (string) null; this.<>t__builder.SetResult(content52); ``` > 以上四个状态的贴脸源码均截取自ILspy反编译结果,读者可将代码和状态轮转图对比。 *** ** * ** *** 一线码农大佬讲: 一个简单成功的async/await状态机会经历 2次`MoveNext`动作 ,我是认同的。 一次是状态机启动,主动切换状态; 第二次是IO数据就绪,回调函数会执行原状态机的`MoveNext`方法, 这个是在注册回调的时候确定的。 下面是第二次`MoveNext`方法的执行堆栈(包含github地址): * [第二次StateMachine.MoveNext](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FOmerMor%2FAsyncBridge%2Fblob%2Faa806b61be32363934378ffc2df979dc8b34ea88%2Fsrc%2FAsyncBridge%2FRuntime.CompilerServices%2FAsyncMethodBuilderCore.cs%23L137 "https://github.com/OmerMor/AsyncBridge/blob/aa806b61be32363934378ffc2df979dc8b34ea88/src/AsyncBridge/Runtime.CompilerServices/AsyncMethodBuilderCore.cs#L137") * [moveNextRunner.Run()](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FOmerMor%2FAsyncBridge%2Fblob%2Faa806b61be32363934378ffc2df979dc8b34ea88%2Fsrc%2FAsyncBridge%2FRuntime.CompilerServices%2FAsyncMethodBuilderCore.cs%23L64 "https://github.com/OmerMor/AsyncBridge/blob/aa806b61be32363934378ffc2df979dc8b34ea88/src/AsyncBridge/Runtime.CompilerServices/AsyncMethodBuilderCore.cs#L64"); * [GetCompletionAction回调方法](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FOmerMor%2FAsyncBridge%2Fblob%2Faa806b61be32363934378ffc2df979dc8b34ea88%2Fsrc%2FAsyncBridge%2FRuntime.CompilerServices%2FAsyncMethodBuilderCore.cs%23L59C25-L59C44 "https://github.com/OmerMor/AsyncBridge/blob/aa806b61be32363934378ffc2df979dc8b34ea88/src/AsyncBridge/Runtime.CompilerServices/AsyncMethodBuilderCore.cs#L59C25-L59C44") * [AwaitUnsafeOnCompleted注册回调](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FOmerMor%2FAsyncBridge%2Fblob%2Faa806b61be32363934378ffc2df979dc8b34ea88%2Fsrc%2FAsyncBridge%2FRuntime.CompilerServices%2FAsyncTaskMethodBuilder.cs%23L334C50-L334C69 "https://github.com/OmerMor/AsyncBridge/blob/aa806b61be32363934378ffc2df979dc8b34ea88/src/AsyncBridge/Runtime.CompilerServices/AsyncTaskMethodBuilder.cs#L334C50-L334C69") ### 结束语 本文重点从状态机设计模式的角度,演示了async/await语法糖的内部实现。 通过一个骚动的机器引擎,演示了开启异步任务---\> 异步任务完成---\> 设置状态机输出结果的全过程,而这4个状态的变迁又催生了.NET异步编程的带来的性能优势。 ![](https://file.jishuzhan.net/article/1780052848025800705/27a9572a820457eb71702c3b65968fec.webp) 最后:本文是一线码农大佬(博客园12349粉丝博主)《异步async/await底朝天》的狗尾续貂,respect !!!

相关推荐
yeyong29 分钟前
用springboot开发一个snmp采集程序,并最终生成拓扑图(三)
后端
倚栏听风雨1 小时前
IDEA 插件开发 plugin.xml 中 <depends config-file=".xml" optional="true"> 详解
后端
惜鸟1 小时前
Spring Boot项目自己封装一个分页查询工具
spring boot·后端
Dithyrambus1 小时前
ObjectScript 中文入门教程
后端
林太白2 小时前
也许看了Electron你会理解Tauri,扩宽你的技术栈
前端·后端·electron
松果集2 小时前
【Python3】练习一
后端
anganing2 小时前
Web 浏览器预览 Excel 及打印
前端·后端
肯定慧2 小时前
B1-基于大模型的智能办公应用软件
后端
TinyKing2 小时前
一、getByRole 的作用
后端