• async/await 贴脸输出,这次你总该明白了


    出来混总是要还的

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

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

    [一线码农大佬]在博客园2020年写的《await,async 我要把它翻个底朝天,这回你总该明白了吧》手把手实现了异步状态机,这篇文章很是经典, 但是评论区很多人还是在吐槽看不懂, 我也看的不是很懂。

    以我浅薄的推测:

    1. 一线大佬的知识体系太宽太深,有的验证点在文字之外,需要我们自己去确认。
    2. 有些内容太细节,挖的太深,出不来。
    3. 很多人不熟悉状态机设计模式, 导致看大佬文章,知其然不知其所以然。

    我以前用Go语言演示了状态机: 我是状态机,有一颗永远骚动的机器引擎, 当时有粉丝留言让用.NET 实现状态机, 这篇文章也算是对粉丝的喊话。

    状态机:一颗永远骚动的机器引擎

    状态机是一种行为设计模式,它允许对象在其内部状态改变时改变其行为。看起来好像对象改变了它的类。

    请仔细理解上面每一个字。

    我们以自动售货机为例,为简化演示,我们假设自动售货机只有1种商品, 故自动售货机有itemCountitemPrice 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所在的函数名命名,示例状态机为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<GetResult>d__1>(ref stateMachine);
    return stateMachine.<>t__builder.Task;

    车钥匙Start,内部实际是执行MoveNext方法
    该方法会设置异步任务的TaskAwaiter对象, 紧接着stateMachine进入新的状态。

    2. 异步任务未完成状态

    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获取异步任务结果;
    • 执行后继代码;
    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;
    • 设置状态机最终返回值;
    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地址):

    结束语

    本文重点从状态机设计模式的角度,演示了async/await语法糖的内部实现。

    通过一个骚动的机器引擎,演示了开启异步任务---> 异步任务完成---> 设置状态机输出结果的全过程,而这4个状态的变迁又催生了.NET异步编程的带来的性能优势。

    最后:本文是一线码农大佬(博客园12349粉丝博主)《异步async/await底朝天》的狗尾续貂,respect !!!

  • 相关阅读:
    【从零开始游戏开发】Lua热更新概念与基础知识讲解 | 全面总结 |建议收藏
    linux 系统时间不正确,如何查看ntp是否同步
    架构升级实践
    C++ string赋值和添加值
    [游戏开发][Unity] ScriptableObject数据创建与各种加载方式
    FFmpeg和rtsp服务器搭建视频直播流服务
    Nginx简单使用
    Hawkeye
    paddlepaddle模型的保存和加载
    TCP 四次挥手,可以直接变成三次?
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/18137189