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

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

内容简介:其实这篇文章的内容可以参考《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 ,以上三步就要在状态机中重复几次。


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

查看所有标签

猜你喜欢:

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

云攻略

云攻略

马克·贝尼奥夫、卡莱尔·阿德勒 / 徐杰 / 海天出版社 / 2010年8月 / 36.00元

Apple、Google、甲骨文、腾讯 都已投入了云的怀抱, 你还在等什么? 快来加入我们! 最初,Salesforce.com 只是一间小小的租赁公寓 在短短10年内 它已成长为 世界上发展最快、最具创新力的 产业变革领导者 曾经,这是个软件为王的时代。 现在,这是个云计算的新时代。 NO SOFTWARE 抛弃软件的......一起来看看 《云攻略》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具