一:背景

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 style="box-sizing: border-box;"> \d__1> 类

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


.class nested private auto ansi sealed beforefieldinit '<GetResult>d__1'extends [System.Runtime]System.Objectimplements [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 style="box-sizing: border-box;"> 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.0IL_0006: ldloc.0IL_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.0IL_0012: ldc.i4.m1IL_0013: stfld int32 ConsoleApp3.Program/'<GetResult>d__1'::'<>1__state'IL_0018: ldloc.0IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'IL_001e: ldloca.s 0IL_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.0IL_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 style="box-sizing: border-box;"> 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

其实你仔细观察会发现,所谓的 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 : ICriticalNotifyCompletionwhere 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# ,或许你以前很多不明白的地方此时都会豁然开朗,不是吗?

await,async 我要把它翻个底朝天,这回你总该明白了吧相关推荐

  1. 使用await / async时,HttpClient.GetAsync(...)永远不会返回

    本文翻译自:HttpClient.GetAsync(-) never returns when using await/async Edit: This question looks like it ...

  2. 多线程-Task、await/async

    Task创建无返回值 Task是.netframwork4.0重新分装的多线程类.原因以前的多线程(thread threadpool)不好用.(.net framwork也是的发展的,现在的EF,刚 ...

  3. vscode vue解决跨域_Vue + WebPack + Typescript初学者VSCode项目 (按需加载、跨域调试、await/async)...

    万事开头难,一个好的Hello World程序可以节省我们好多的学习时间,帮助我们快速入门.Hello World程序之所以是入门必读必会,就是因为其代码量少,简单易懂.但我觉得,还应该做到功能丰富, ...

  4. C#中await/async闲说

    自从C#5.0增加异步编程之后,异步编程越来越简单,async和await用的地方越来越多,越来越好用,只要用异步的地方都是一连串的异步,如果想要异步编程的时候,需要从底层开始编写,这样后边使用的时候 ...

  5. 快速理解和使用 ES7 await/async

    await/async 是 ES7 最重要特性之一,它是目前为止 JS 最佳的异步解决方案了.虽然没有在 ES2016 中录入,但很快就到来,目前已经在 ES-Next Stage 4 阶段. 直接上 ...

  6. promise 的基本概念 和如何解决js中的异步编程问题 对 promis 的 then all ctch 的分析 和 await async 的理解

    * promise承诺 * 解决js中异步编程的问题 * * 异步-同步 * 阻塞-无阻塞 * * 同步和异步的区别? 异步;同步 指的是被请求者 解析:被请求者(该事情的处理者)在处理完事情的时候的 ...

  7. IC卡读卡器web插件与异步await/async接口

    友我IC卡读卡器web插件V2.3增加了异步await/async接口访问模式,使得js程序可读性更强,按照顺序调用即可获取数据结果. 对于异步await/async的接口调用,使用await关键字即 ...

  8. 【转】WebApi中的C#await / async,重点是什么?

    有人知道这样做的目的是什么? private async Task<bool> StoreAsync(TriviaAnswer answer) { ... } [ResponseType( ...

  9. Await Async和Thread.waitAll想法?未完待续

    [管理员]四九-李冰-修行者(2216529884) 2017/7/3 17:15:12看着就可以了,这种东西是有使用场景的.并不是你用了就一定有提升的 [管理员]上海-xx科技(lovepoint7 ...

最新文章

  1. 如何在进程间共享数据
  2. 计算机组成与设计第五版英文_南京大学计算机考研信息汇总
  3. 关于“wap2app仅支持对已通过ICP备案的域名站点进行打包”问题解决
  4. Deploy过程出错解决
  5. oracle session status killed,进程状态为KILLED的进程如何杀掉
  6. pat编程语言_浙江大学在线pat题库集合
  7. 散热器老化引起电脑死机
  8. UNIX 环境高级编程(一) apue.h 文件与apue.3e的安装
  9. android 编译宏,android 添加全局变量宏开关的三种方式
  10. qtableiwdget优化之再优化(大批量数据加载不卡顿)
  11. 计算机毕业论文画图软件,计算机科学与技术专业毕业论文(绘图软件的设计).doc...
  12. CCPP Blog 目录
  13. C++ 定义复数的加减乘除基本运算
  14. 房价,经济转型,技术创新
  15. 玩转 Defcon 黑客大会,这里有份装 X 指南
  16. 【Gephi】初学者教程(一)「一步一步教你怎么画图」「值得放进收藏夹吃灰系列」
  17. 为什么不想做产品经理
  18. 关于Mobius反演
  19. 《Linuxnbsp;Kernelnbsp;Development》读书…
  20. python画一颗小心心

热门文章

  1. Python集合和函数
  2. #UnityTips# 2017.11.14
  3. java小基础之instanceof运算符
  4. UIView中常用的方法
  5. 关于使用indexedDB的本地存储(2)
  6. 【转】Python可变长度的函数参数
  7. JS中数组Array的用法{转载}
  8. 进程handle获取线程_获取进程中的线程列表
  9. plex实现流媒体服务器_Plex继续远离服务器,提供网络节目
  10. android页面布局 如何让中间的listview填充剩余部分_谷歌驾驶设计—界面设计布局...