乐趣区

关于c#:awaitasync-我要把它翻个底朝天这回你总该明白了吧

一:背景

1. 讲故事

await,async 这玩意的知识点曾经被人说的烂的不能再烂了,看似没什么好说的,但我发现有不少文章还是从实践上讲述了这两个语法糖的用法,懂得还是懂,不懂的看似懂了过几天又不懂了,人生如戏全靠记是不行的哈????????????,其实实质上来说 await, async 只是编译器层面上的语法糖,在 IL 层面都会被打成原型的,所以在这个层面上意识这两个语法糖是十分有必要的。

二:从 IL 层面意识

1. 应用 WebClient 下载

为了不便打回原型,我先上一个例子,应用 webclient 异步下载 http://cnblogs.com 的 html,代码如下:


    class Program
    {static void Main(string[] args)
        {var html = GetResult();

            Console.WriteLine("稍等... 正在下载 cnblogs -> html \r\n");

            var content = html.Result;

            Console.WriteLine(content);
        }

        static async Task<string> GetResult()
        {var client = new WebClient();

            var content = await client.DownloadStringTaskAsync(new Uri("http://cnblogs.com"));

            return content;
        }
    }

下面的代码非常简单,能够看到异步操作没有阻塞主线程输入: 稍等... 正在下载 cnblogs -> html \r\n, 编译器层面没什么好说的,接下来看下在 IL 层面产生了什么?

2. 开掘 await async 的 IL 代码

还是老规矩,ilSpy 走起,如下图:

能够看到,这里有一个 GetResult 办法,一个 Main 办法,还有一个不晓得在哪里冒出来的 <GetResult>d__1 类,接下来和大家一个一个聊。

<1> <GetResult>d__1> 类

因为不晓得从哪里冒出来的,特地引人关注,所以看看它的 IL 是咋样的?


.class nested private auto ansi sealed beforefieldinit '<GetResult>d__1'
    extends [System.Runtime]System.Object
    implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine
{
    .method private final hidebysig newslot virtual 
        instance void MoveNext () cil managed 
    { }

    .method private final hidebysig newslot virtual 
        instance void SetStateMachine (class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
        ) cil managed 
    {}}

从下面的 IL 代码能够看到,这是主动生成的 <GetResult>d__1 类实现了接口 IAsyncStateMachine,定义如下:

看到外面的 MoveNext 是不是很眼生,平时你在 foreach 汇合的时候就会用到这个办法,那时人家叫做枚举类,在这里算是被革新了一下,叫状态机????????????。

<2> GetResult ()

为了不便演示,我对办法体中的 IL 代码做一下简化:


.method private hidebysig static 
    class [System.Runtime]System.Threading.Tasks.Task`1<string> GetResult () cil managed 
{IL_0000: newobj instance void ConsoleApp3.Program/'<GetResult>d__1'::.ctor()
    IL_0005: stloc.0
    IL_0006: ldloc.0
    IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Create()
    IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'
    IL_0011: ldloc.0
    IL_0012: ldc.i4.m1
    IL_0013: stfld int32 ConsoleApp3.Program/'<GetResult>d__1'::'<>1__state'
    IL_0018: ldloc.0
    IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'
    IL_001e: ldloca.s 0
    IL_0020: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Start<class ConsoleApp3.Program/'<GetResult>d__1'>(!!0&)
    IL_0025: ldloc.0
    IL_0026: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'
    IL_002b: call instance class [System.Runtime]System.Threading.Tasks.Task`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::get_Task()
    IL_0030: ret
} // end of method Program::GetResult

如果你略微懂一点的话,在 IL_0000 处的 newobj 你就应该晓得这个办法就是做了 new <GetResult>d__1,而后从 IL_002b 处返回了一个 get_Task(),这时候你就应该明确,为什么主线程不会被阻塞,因为人家返回的是 Task<string>,对吧,最初的 http 后果会藏在 Task<string> 中,这样是不是就很好了解了。

<3> Main

Main 办法没有做任何扭转,原来是什么样当初还是什么样。

三:将 IL 代码 回写为 C

1. 残缺 C# 代码

通过后面一部分你应该对 await,async 在 IL 层面有了一个框架性的意识,这里我就全副反写成 C# 代码:


    class Program
    {static void Main(string[] args)
        {var html = GetResult();

            Console.WriteLine("稍等... 正在下载 cnblogs -> html \r\n");

            var content = html.Result;

            Console.WriteLine(content);
        }

        static Task<string> GetResult()
        {GetResult stateMachine = new GetResult();

            stateMachine.builder = AsyncTaskMethodBuilder<string>.Create();

            stateMachine.state = -1;

            stateMachine.builder.Start(ref stateMachine);

            return stateMachine.builder.Task;
        }
    }

    class GetResult : IAsyncStateMachine
    {
        public int state;
        public AsyncTaskMethodBuilder<string> builder;
        private WebClient client;
        private string content;
        private string s3;
        private TaskAwaiter<string> awaiter;

        public void MoveNext()
        {
            var result = string.Empty;
            TaskAwaiter<string> localAwaiter;
            GetResult stateMachine;

            int num = state;

            try
            {if (num == 0)
                {
                    localAwaiter = awaiter;
                    awaiter = default(TaskAwaiter<string>);
                    num = state = -1;
                }
                else
                {client = new WebClient();

                    localAwaiter = client.DownloadStringTaskAsync(new Uri("http://cnblogs.com")).GetAwaiter();

                    if (!localAwaiter.IsCompleted)
                    {
                        num = state = 0;
                        awaiter = localAwaiter;
                        stateMachine = this;
                        builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine);
                        return;
                    }
                }

                s3 = localAwaiter.GetResult();
                content = s3;
                s3 = null;
                result = content;
            }
            catch (Exception exx)
            {
                state = -2;
                client = null;
                content = null;
                builder.SetException(exx);
            }

            state = -2;
            client = null;
            content = null;
            builder.SetResult(result);
        }

        public void SetStateMachine(IAsyncStateMachine stateMachine) {}}

能够看到,回写成 C# 代码之后跑起来是没有任何问题的,为了不便了解,我先来画一张流程图。

通过下面的 xmind,它根本流程就是:stateMachine.builder.Start(ref stateMachine) -> GetResult.MoveNext -> client.DownloadStringTaskAsync -> localAwaiter.IsCompleted = false -> builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine) -> GetResult.MoveNext -> localAwaiter.GetResult() -> builder.SetResult(result)

2. 分析 AsyncTaskMethodBuilder<string>

其实你仔细观察会发现,所谓的 await,async 的异步化运作都是由 AsyncTaskMethodBuilder 承载的,如异步工作的启动,对 html 后果的封送,接触底层 IO,其中 Task<string> 对应着 AsyncTaskMethodBuilder<string>, Task 对应着 AsyncTaskMethodBuilder,这也是为什么编译器在 async 处始终提醒你返回 Task 和 Task<string>,如果不这样的话的就找不到对应 AsyncTaskMethodBuilder 了,对吧,如下图:

而后着重看下 AwaitUnsafeOnCompleted 办法,这个办法十分重要,其正文如下:


        //
        // Summary:
        //     Schedules the state machine to proceed to the next action when the specified
        //     awaiter completes. This method can be called from partially trusted code.
        public void AwaitUnsafeOnCompleted<[NullableAttribute(0)] TAwaiter, [NullableAttribute(0)] TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
            where TAwaiter : ICriticalNotifyCompletion
            where TStateMachine : IAsyncStateMachine;

一旦调用了这个办法,就须要期待 底层 IO 将工作处理完毕之后二次回调 GetResult.MoveNext,也就示意要么异样要么实现工作,Awaiter 包装的 Task 后果封送到 builder.SetResult

而后简略说一下 状态机 的走法,通过调试会发现这里会走 两次 MoveNext,一次启动,一次拿后果。

<1> 第一次回调 MoveNext

第一次 MoveNext 的触发由 stateMachine.builder.Start(ref stateMachine) 发动,能够用 dnspy 去调试一下,如下图:

<2> 第二次回调 MoveNext

第二次 MoveNext 的触发由 builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine) 开始,能够看到一旦 网络驱动程序 处理完毕后就由线程池 IO 线程被动发动到最初触发代码中的 MoveNext,最初就是到 awaiter 中获取 task 的 result 处完结,如下图:

四:总结

语法糖有简略和简单之分,简单的也不要怕,学会将 IL 代码翻译成 C#,或者你以前很多不明确的中央此时都会恍然大悟,不是吗?

退出移动版