async/await 贴脸输出,这次你总该明白了
出来混总是要还的
最近在准备记录一个.NET Go核心能力的深度对比, 关于.NET/Go的异步实现总感觉没敲到点上。
async/await是.NET界老生常谈的话题,每至于此,状态机又是必聊的话题,但是状态机又是比较晦涩难懂的话题。
[一线码农大佬]在博客园2020年写的《await,async 我要把它翻个底朝天,这回你总该明白了吧》手把手实现了异步状态机,这篇文章很是经典, 但是评论区很多人还是在吐槽看不懂, 我也看的不是很懂。
以我浅薄的推测:
- 一线大佬的知识体系太宽太深,有的验证点在文字之外,需要我们自己去确认。
- 有些内容太细节,挖的太深,出不来。
- 很多人不熟悉状态机设计模式, 导致看大佬文章,知其然不知其所以然。
我以前用Go语言演示了状态机: 我是状态机,有一颗永远骚动的机器引擎, 当时有粉丝留言让用.NET 实现状态机, 这篇文章也算是对粉丝的喊话。
状态机:一颗永远骚动的机器引擎
状态机是一种行为设计模式,它允许对象在其内部状态改变时改变其行为。看起来好像对象改变了它的类。
请仔细理解上面每一个字。
我们以自动售货机为例,为简化演示,我们假设自动售货机只有1种商品, 故自动售货机有itemCount 、itemPrice 2个属性
不考虑动作的前后相关性,自动售货机对外暴露4种行为:
- 给自动售货机加货
addItem - 选择商品
requestItem - 付钱
insertMoney - 出货
dispenseItem
重点来了,当发生某种行为,自动售货机会进入如下4种状态之一, 并据此状态做出特定动作, 之后进入另外一种状态.....
- 有商品
hasItem - 无商品
noItem - 已经选好商品
itemRequested - 已付钱
hasMoney

当对象可能处于多种不同的状态之一、根据传入的动作更改当前的状态, 继续接受后续动作,状态再次发生变化.....
这样的模式类比于机器引擎,周而复始的工作和状态转化,这也是状态机的定语叫“机Machine”的原因。
有了以上思路,我们尝试沟通UML 伪代码

状态机设计模式的伪代码实现:
- 所谓的机器Machine维护了状态切换的上下文
- 机器对外暴露的行为,驱动机器的状态变更
- 机器到达特定的状态 只具备特定的行为,其他行为是不被允许的
Go版本的售货机(状态机设计模式)的源码,请参见原文https://www.cnblogs.com/JulianHuang/p/15304184.html。
对async/await贴脸开大
还是以一线码农大佬的异步下载为例:

编译器词法分析定位到async/await语法糖,就会为开发者生成状态机代码。
一个新出炉的状态机包含如下属性 :

(1) 初始化的状态机,以async所在的函数名命名,示例状态机为<GetResult>d__1;
(2)车钥匙启动状态机之后,立马返回,这正是异步编程的内涵。
一个简单的、成功的状态机转化如图:

1. 初始状态
- state= -1;
- Start状态机; 即时返回。
Program.<GetResult>d__1 stateMachine = new Program.<GetResult>d__1();
stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start<Program.<GetResult>d__1>(ref stateMachine);
return stateMachine.<>t__builder.Task;
车钥匙Start,内部实际是执行MoveNext方法,
该方法会设置异步任务的TaskAwaiter对象, 紧接着stateMachine进入新的状态。
2. 异步任务未完成状态
- 切换到state = 0
- 调用
AwaitUnsafeOnCompleted()向底层注册回调, 2个入参- 回调参数1:异步结果
TaskAwaiter<TResult> - 回调参数2: 当前状态机
- 回调参数1:异步结果
int num1 = this.<>1__state;
if (num1 != 0)
{
this.<client>5__1 = new WebClient();
awaiter = this.<client>5__1.DownloadStringTaskAsync(new Uri("http://cnblogs.com")).GetAwaiter();
if (!awaiter.IsCompleted)
{
this.<>1__state = num2 = 0;
this.<>u__1 = awaiter;
Program.<GetResult>d__1 stateMachine = this;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, Program.<GetResult>d__1>(ref awaiter, ref stateMachine);
return;
}
}
IO数据就绪,会在IO线程执行回调方法GetCompletionAction,利用入参2:状态机,再次执行状态机的MoveNext方法, 进入新的状态
3. 异步结果就绪状态
- 切换到state = -1;
- taskAwaiter获取异步任务结果;
- 执行后继代码;
else
{
awaiter = this.<>u__1;
this.<>u__1 = new TaskAwaiter<string>();
this.<>1__state = num2 = -1;
}
this.<>s__3 = awaiter.GetResult();
this.<content>5__2 = this.<>s__3;
this.<>s__3 = (string) null;
content52 = this.<content>5__2; // 后继代码段
若无异常,则进入状态机终止状态。
4. 状态机终止状态
- 切换到state =-2;
- 设置状态机最终返回值;
this.<>1__state = -2;
this.<client>5__1 = (WebClient) null;
this.<content>5__2 = (string) null;
this.<>t__builder.SetResult(content52);
以上四个状态的贴脸源码均截取自ILspy反编译结果,读者可将代码和状态轮转图对比。
一线码农大佬讲: 一个简单成功的async/await状态机会经历 2次MoveNext动作 ,我是认同的。
一次是状态机启动,主动切换状态;
第二次是IO数据就绪,回调函数会执行原状态机的MoveNext方法, 这个是在注册回调的时候确定的。
下面是第二次MoveNext方法的执行堆栈(包含github地址):
结束语
本文重点从状态机设计模式的角度,演示了async/await语法糖的内部实现。
通过一个骚动的机器引擎,演示了开启异步任务---> 异步任务完成---> 设置状态机输出结果的全过程,而这4个状态的变迁又催生了.NET异步编程的性能优势。

最后:本文是一线码农大佬(博客园12349粉丝博主)《异步async/await底朝天》的狗尾续貂,respect !!!
async/await 贴脸输出,这次你总该明白了的更多相关文章
- 答应我,这次必须搞懂!痛点难点Promise。(小点心async/await,基于Promise的更优方案)
Promise 出现的原因 在 Promise 出现以前,我们处理一个异步网络请求,大概是这样: // 请求 代表 一个异步网络调用. // 请求结果 代表网络请求的响应. 请求1(function( ...
- 译文: async/await SynchronizationContext 上下文问题
async / await 使异步代码更容易写,因为它隐藏了很多细节. 许多这些细节都捕获在 SynchronizationContext 中,这些可能会改变异步代码的行为完全由于你执行你的代码的环境 ...
- Python PEP 492 中文翻译——协程与async/await语法
原文标题:PEP 0492 -- Coroutines with async and await syntax 原文链接:https://www.python.org/dev/peps/pep-049 ...
- C# 中 async/await 调用传统 Begin/End 异步方法
最近在改进园子的图片上传程序,希望实现用户上传图片时同时将图片文件保存在三个地方:1)服务器本地硬盘:2)又拍云:3)阿里云OSS.并且在保存时使用异步操作. 对于异步保存到本地硬盘,只需用 Stea ...
- 从C#到TypeScript - async await
总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...
- promise async await使用
1.Promise (名字含义:promise为承诺,表示其他手段无法改变) Promise 对象代表一个异步操作,其不受外界影响,有三种状态: Pending(进行中.未完成的) Resolved( ...
- 为什么 array.foreach 不支持 async/await
一.背景 react 项目中,渲染组件时,显示的数据一直有问题,本来以为是 react 组件的问题,后来才发现罪魁祸首在 fetch 数据的过程,因为我用了 async/await ,而却搭配了 fo ...
- Promise及Async/Await
一.为什么有Async/Await? 我们都知道已经有了Promise的解决方案了,为什么还要ES7提出新的Async/Await标准呢? 答案其实也显而易见:Promise虽然跳出了异步嵌套的怪 ...
- 关于C#中async/await中的异常处理(下)-(转载)
上一篇文章里我们讨论了某些async/await的用法中出现遗漏异常的情况,并且谈到该如何使用WhenAll辅助方法来避免这种情况.WhenAll辅助方法将会汇总一系列的任务对象,一旦其中某个出错,则 ...
- [翻译] Python 3.5中async/await的工作机制
Python 3.5中async/await的工作机制 多处翻译出于自己理解,如有疑惑请参考原文 原文链接 身为Python核心开发组的成员,我对于这门语言的各种细节充满好奇.尽管我很清楚自己不可能对 ...
随机推荐
- Twitter推特 api接口 获取trending趋势搜索关键词 python数据采集
iDataRiver平台 https://www.idatariver.com/zh-cn/ 提供开箱即用的Twitter公开数据采集API,供用户按需调用. 接口使用详情请参考Twitter接口文档 ...
- c语言中float和double类型的区别
1.变量bai类型不同 float属于单du精度zhi型浮点数据. double属于双精度型浮点数据. 2.指数范围不同 float的指数范围为-127~128. double而double的指数范围 ...
- 基于ESP8266的JSON解析实例分析
什么是JSON? JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.采用完全独立于编程语言的文本格式来存储和表示数据.其简洁和层次结构清晰的特点使得 J ...
- Kinaba discover查询语法
1.要搜索一个确切的字符串,即精确搜索,需要使用双引号引起来:path:"/app/logs/nginx/access.log" 2.如果不带引号,将会匹配每个单词:uid tok ...
- 小米Linux 运维工程师面试真题
小米Linux 运维工程师面试真题 首先我们来看下小米 Linux 运维工程师招聘岗位要求: [岗位定义]运维工程师 [岗位薪资]10K-24K [基本要求]北京 / 经验 3-5 年 / 本科及以上 ...
- 记录--工程化第一步这个package.json要真的搞明白才行
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 工程化最开始就是package.json开始的,很多人学了很多年也没搞清楚这个为什么这么神奇,其实有些字段是在特定场景才有效的,那每个属性 ...
- 记录--居中为什么要使用 transform?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 引言 居中是我们在前端布局中经常会遇到的问题,其中包括水平居中和垂直居中.居中的方法很多,比如说水平居中可以使用text-align: c ...
- 记录转载:Vite多环境配置--让项目拥有更高定制化能力
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 业务背景 近些年来,随着前端工程架构发展,使得前端项目中也能拥有如后端工程的模块能力.正所谓 "能力(越)越大(来),责任(越) ...
- Shell脚本关闭Nginx进程
[root@testapp ~]# ps -ef | grep nginx root 25265 25216 0 09:22 pts/0 00:00:00 grep --color=auto ngin ...
- 探索华为云CCE敏捷版金融级高可用方案实践案例
本文分享自华为云社区<华为云CCE敏捷版金融级高可用方案实践>,作者: 云容器大未来. 一.背景 1.1. CCE 敏捷版介绍 云原生技术有利于各组织在公有云.私有云和混合云等新型动态环境 ...