一:背景

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

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

await,async 我要把它翻个底朝天,这回你总该明白了吧的更多相关文章

  1. javascript异步编程的前世今生,从onclick到await/async

    javascript与异步编程 为了避免资源管理等复杂性的问题, javascript被设计为单线程的语言,即使有了html5 worker,也不能直接访问dom. javascript 设计之初是为 ...

  2. Silverlight项目笔记1:UI控件与布局、MVVM、数据绑定、await/async、Linq查询、WCF RIA Services、序列化、委托与事件

    最近从技术支持转到开发岗,做Silverlight部分的开发,用的Prism+MVVM,框架由同事搭好,目前做的主要是功能实现,用到了一些东西,侧重于如何使用,总结如下 1.UI控件与布局 常用的主要 ...

  3. 5分种让你了解javascript异步编程的前世今生,从onclick到await/async

      javascript与异步编程 为了避免资源管理等复杂性的问题,javascript被设计为单线程的语言,即使有了html5 worker,也不能直接访问dom. javascript 设计之初是 ...

  4. .Net 异步方法, await async 使用

    最近朋友问起await  和 async第一次听说这个await ,就查了一下这个await使用在于 异步方法async 中,中文意思就是等待,经过一系列的百度参考简单的明白了这个东西的意思,  异步 ...

  5. es7 await/async解决异步问题

    最近做项目遇到一个问题,前端调用ie浏览器中的ocx的方法去查询数据,查询完之后ocx给一个返回值,然后js将返回值当参数传入到另外的函数中去做数据处理,但是遇到一个问题是前端需要异步去执行这个过程 ...

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

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

  7. 如何避免 await/async 地狱

    原文地址:How to escape async/await hell 译文出自:夜色镇歌的个人博客 async/await 把我们从回调地狱中解救了出来,但是如果滥用就会掉进 async/await ...

  8. 【.NET异步编程系列1】:await&async语法糖让异步编程如鱼得水

    前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现. IAsyncResult Beg ...

  9. 使用Typescript写的Vue初学者Hello World实例(实现按需加载、跨域调试、await/async)

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

随机推荐

  1. PHP tan() 函数

    实例 返回不同数的正切: <?php高佣联盟 www.cgewang.comecho(tan(M_PI_4) . "<br>");echo(tan(0.50) . ...

  2. PHP str_rot13() 函数

    实例 编码并解码字符串: <?php高佣联盟 www.cgewang.comecho str_rot13("Hello World");echo "<br&g ...

  3. PDO::prepare

    PDO::prepare — 准备要执行的SQL语句并返回一个 PDOStatement 对象(PHP 5 >= 5.1.0, PECL pdo >= 0.1.0) 说明 语法 publi ...

  4. JS&ES6学习笔记(持续更新)

    ES6学习笔记(2019.7.29) 目录 ES6学习笔记(2019.7.29) let和const let let 基本用法 let 不存在变量提升 暂时性死区 不允许重复声明 块级作用域 级作用域 ...

  5. java数组输出的三种方式

    第一种:foreach语句遍历输出 //通过foreach语句遍历输出数组 int nums[] = new int [4]; for (int num:nums) { System.out.prin ...

  6. ubuntu apt-get 安装找不到包问题

    1.首先 sudo gedit /etc/apt/sources.list  删除全部换成国内源 (推荐163) 2.考虑 ubuntu apt-get update失败 1.出现错误:E:Could ...

  7. Mixed Precision Training —— caffe-float16

    简介 最近有了突如其来的想法,如何把caffe的变得更小更快.后来翻到Nvidia开发caffe-float16,同时也看到它的论文.看完大致了解一番后,就做一下记录. 该工作的目标是,减少网络的所需 ...

  8. List集合遍历时修改元素出现并发修改异常总结

    什么是并发修改异常: 当我们在遍历实现了collection接口与iterator接口的集合时(List.Set.Map), 我们可以通过遍历索引也可以通过迭代器进行遍历.在我们使用迭代器进行遍历集合 ...

  9. XSS 渗透思路笔记

    了解XSS首先要了解HTML里面的元素:共有5种元素:空元素.原始文本元素. RCDATA元素.外来元素以及常规元素. 空元素area.base.br.col. command. embed.hr.i ...

  10. 滴滴推理引擎IFX:千万规模设备下AI部署实践

    桔妹导读:「滴滴技术」将于本月开始,联合各技术团队为大家带来精彩分享.你想了解的技术干货,深度专访,团队及招聘将于每周三与你准时见面.本月为「滴滴云平台事业群分享月」,在今天的内容中,云平台事业群-机 ...