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


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

查看所有标签

猜你喜欢:

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

数据结构与算法JavaScript描述

数据结构与算法JavaScript描述

[美] Michael McMillan / 王群锋、杜 欢 / 人民邮电出版社 / 2014-8 / 49.00元

通过本书的学习,读者将能自如地选择最合适的数据结构与算法,并在JavaScript开发中懂得权衡使用。此外,本书也概述了与数据结构与算法相关的JavaScript特性。 本书主要内容如下。 数组和列表:最常用的数据结构。 栈和队列:与列表类似但更复杂的数据结构。 链表:如何通过它们克服数组的不足。 字典:将数据以键-值对的形式存储。 散列:适用于快速查找和检索。......一起来看看 《数据结构与算法JavaScript描述》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

html转js在线工具
html转js在线工具

html转js在线工具