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 ,以上三步就要在状态机中重复几次。


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

查看所有标签

猜你喜欢:

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

Google是如何控制世界的

Google是如何控制世界的

(美)丹尼尔·伊克比亚 / 李军 / 东方出版社 / 2008-08 / 36.00元

秘Google的发展之路! Google,这个有着数百亿的网页存储量、每天两亿搜索次数的搜索引擎,最初仅仅是一个方程式。这个由拉里·佩奇和塞吉·布林两位天才创造出的超级算法甚至比可口可乐的配方还要保密。 当广告公司为自己网页在搜索结果中的排序争得头破血流时,Google正悠然地坐收渔翁之利,这种天才的拍卖广告链接的商业模式给Google带来了令人瞠目结舌的企业利润!仅仅从1999~20......一起来看看 《Google是如何控制世界的》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具