C# async await 原理:编译器如何将异步函数转换成状态机

栏目: ASP.NET · 发布时间: 7年前

内容简介:其实这篇文章的内容可以参考《CLR via C#》里的第28章,不过总觉得书上的还不够直观,所以干脆自己来整理一下好了。新建一个控制台项目,代码如下,三个非常简单的方法。现在用

其实这篇文章的内容可以参考《CLR via C#》里的第28章,不过总觉得书上的还不够直观,所以干脆自己来整理一下好了。

新建一个控制台项目,代码如下,三个非常简单的方法。

static async Task<string> MyMethodAsync(int argument)
    {
        var t1 = await GetType1();
        var t2 = await GetType2();
        return "Complete";
    }

    static async Task<Type> GetType1()
    {
        await Task.Run(() => { Console.WriteLine("GetType1"); });
        return typeof(string);
    }

    static async Task<Type> GetType2()
    {
        await Task.Run(() => { Console.WriteLine("GetType2"); });
        return typeof(int);
    }

现在用 ILSpy 将其转换为 C# 4.0 的代码之后(因为 C# 4.0 没有 async await ,所以能看到状态机),我们来看看状态机的原理:

代码经过可读性优化,并非原版。

/* 指出这是一个异步方法,并指出状态机的实现是哪个 class\struct */
    [AsyncStateMachine(typeof(<MyMethodAsync>d__1))]
    [DebuggerStepThrough]
    private static Task<string> MyMethodAsync(int argument)
    {
        // 创建状态机实例并初始化
        <MyMethodAsync>d__1 stateMachine = new <MyMethodAsync>d__1();
        stateMachine.argument = argument;   // 将方法实参拷贝到状态机的字段上
        // 创建 builder ,从这个方法存根(我觉得理解为变量即可)上返回 Task<string>
        stateMachine.t__builder = AsyncTaskMethodBuilder<string>.Create();
        stateMachine.m_state = -1;   // 设置状态的初始位置

        // 开始执行状态机
        stateMachine.t__builder.Start(ref stateMachine);
        return stateMachine.t__builder.Task;   // 返回状态机的 Task
    }

    // 这两个方法不重要,当摆设展示下结构即可
    [AsyncStateMachine(typeof(<GetType1>d__2))]
    [DebuggerStepThrough]
    private static Task<Type> GetType1()

    [AsyncStateMachine(typeof(<GetType2>d__3))]
    [DebuggerStepThrough]
    private static Task<Type> GetType2()

    /* 
        这是状态机本身,Release 下是 struct
        原因:https://stackoverflow.com/questions/23609110/why-are-awaiters-async-await-structs-and-not-classes-can-classes-be-used
        */
    [CompilerGenerated]
    private sealed class <MyMethodAsync>d__1 : IAsyncStateMachine
    {
        /* 当前状态机的位置 */
        public int m_state;
        /* 状态机的 builder */
        public AsyncTaskMethodBuilder<string> t__builder;

        /* 
            MyMethodAsync 方法的实参和局部变量此时会变成状态机的字段
            所以一共有三个
            */
        public int argument;
        private Type m_t1;
        private Type m_t2;

        /* 
            每个 awaiter 类型一个字段,我这里 GetType1 跟 GetType2 都返回的是 Type 类型
            所以这里只有一个字段
            当你的方法中涉及到多个 Awaiter 类型时,就会有多个字段,但任何时候只有最近执行的、完成的那一个字段是重要的
         */
        private TaskAwaiter<Type> m_awaiterType1;

        /*
            这是状态机方法
            */
        void IAsyncStateMachine.MoveNext()
        {
            int num = m_state;
            string result;   // 结果值
            try   // 编译器插入 try 块保证状态机的任务完成
            {
                TaskAwaiter<Type> awaiter1;
                TaskAwaiter<Type> awaiter2;

                switch (num)
                {
                /* 
                    这里理解成case -1就行了,意为状态机第一次执行
                    也就是 var t1 = await GetType1();
                    */
                default:
                    awaiter1 = GetType1().GetAwaiter();   // 调用 GetType1 方法并拿到其 Awaiter
                    if (!awaiter1.IsCompleted)   // 这里做判断是避免多次执行
                    {
                        num = (m_state = 0);   // 等一会儿返回状态机时走到哪个位置去
                        m_awaiterType1 = awaiter1;  // 保存 awaiter 以便将来返回
                        stateMachine = this;
                        /* 
                            告诉 awaiter 在 GetType1 的异步方法执行完毕后,调用 MoveNext
                            具体一点,这句代码会调用 awaiterType1 的 OnCompleted
                            它会在被等待的任务上调用 ContinueWith(t=> MoveNext()...
                            表示任务完成后调用 MoveNext 方法重返状态机
                            */
                        t__builder.AwaitUnsafeOnCompleted(ref awaiter1, ref stateMachine);

                        /* 
                            线程返回至调用者
                            */
                        return;
                    }
                    goto case unNamed;
                /* GetType1 方法异步完成了 */
                case 0:
                    awaiter1 = m_awaiterType1;   // 恢复最新的 awaiter

                    m_t1 = awaiter1.GetResult();  // 获取 GetType1 方法的结果
                    // 开始调用 GetType2
                    awaiter2 = GetType2().GetAwaiter();
                    if (!awaiter2.IsCompleted)
                    {
                        num = (m_state = 1);   // 下一次 MoveNext 时走到 case 1 中
                        m_awaiterType1 = awaiter2;
                        t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
                        return;
                    }
                    break;
                case 1:
                    {
                        awaiter2 = m_awaiterType1;   // 恢复最新的 awaiter

                        m_t2 = awaiter2.GetResult();   // 获取 GetType2 方法的结果
                        result = "Complete";   // 设置 MyMethodAsync 方法最终的返回结果
                        // 注意这里没有 break,还要继续执行
                    }
            }
            catch (Exception exception)
            {
                // 有异常:通过设置异常来完成状态机的 Task
                t__builder.SetException(exception);
                return;
            }
            // 无异常:通过返回结果来完成状态机的 Task
            t__builder.SetResult(result);
        }
    }

状态机总结

简单总结以下状态机的几个工作要点:

  1. 安排好 awaiter 之后就 返回原调用线程避免阻塞
  2. 在安排时 更新状态机恢复之后需要执行的位置( m_state
  3. 安排后在 awaiter 的任务上调用 ContinueWith(t=> MoveNext()) 来以便返回状态机

可以简单理解为你的 async 方法中有几个 await ,以上三步就要在状态机中重复几次。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Android编程权威指南(第3版)

Android编程权威指南(第3版)

比尔·菲利普斯 (Bill Phillips)、克里斯·斯图尔特 (Chris Stewart)、克莉丝汀·马西卡诺 (Kristin Marsicano) / 王明发 / 人民邮电出版社 / 2017-6 / 129.00元

Big Nerd Ranch 是美国一家专业的移动开发技术培训机构。本书主要以其Android 训练营教学课程为基础,融合了几位作者多年的心得体会,是一本完全面向实战的Android 编程权威指南。全书共36 章,详细介绍了8 个Android 应用的开发过程。通过这些精心设计的应用,读者可掌握很多重要的理论知识和开发技巧,获得宝贵的开发经验。 第3 版较之前版本增加了对数据绑定等新工具的介......一起来看看 《Android编程权威指南(第3版)》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具