Async 与 Await 关键字研究

1        Aynsc 和 Await 关键字的研究

在 .NET 4.0 以后,基于 Task 的异步编程模式大行其道,因其大大简化了异步编程所带来的大量代码工作而深受编程人员的欢迎,如果你曾经使用过
APM(基于 IAsyncResult) 和 EAP( 基于 event/delegate),那么你一定感受颇深。

而随之而来.NET 4.5 的两个关键字 async 和 await 又使得异步编程如编写顺序的代码一样容易,特别是 async 对
委托(Lamda/LINQ 表达式,匿名委托)的支持,使得async 和 await 成为异步编程的代名词。

但是我们都知道,异步编程的背后是多线程的技术,线程处理,线程间的通讯,线程的管理一直是编程世界里比较难于掌握的部分,那么 async 和 await
关键字究竟有什么魔法能够把复杂的线程处理变成简单的两个关键字而已那?

我相信对 .NET
框架有过了解的人都知道,微软喜欢把底层的东西大肆的进行封装,力求把用户当成傻子看(也不是不好,用户其实不需要知道产品细节),这样使得C#/.Net
容易学习,但是也使得程序员知其然而不知其所以然。

那么我们怎么学习 .NET
光亮功能背后的关键字的技术及其实现原理那?读文章,但是还有一种方式,看源代码(MSIL),使用反编译工具如(Reflector)
查看编译的代码以了解背后的运行机制,使用这种方式我们知道了 using, lock, event, delegate 的背后机制,我们依样画葫芦来解析一下
async 与 await 关键字。

在开始之前,我们先看一段 C# 代码:

static void Main(string[] args)

{

CountAsync();

Console.WriteLine("Async run back to main");

Console.Read();

}

private static async
void CountAsync()

{

Console.WriteLine("Async run ");

await Task.Run(() =>

{

for (int i = 0; i < 10; i++)

{

Console.WriteLine(i);

Thread.Sleep(100);

}

}

);

Console.WriteLine("Async completed");

}

然后看一下他的输出, async 与 await 工作的如同 MSDN 所说的一致(废话01): -- 当方法执行到 await
时,控制权返回调用方,然后等待方法执行完成获取控制权,然后执行 await 后的代码。

那么现在我们用反编译器这个照妖镜来照照这一个 async 和 await 究竟是何方妖魔 : ( 注意要勾选显示编译代码):

神马?怎么多出来这么多不知所谓的东西,我明明只编写了两个方法 Main 和 CountAsync, 其它的是神马东东。
微软,不编译器你究竟对我的代码做了什么,让她(它)变成这样了?

好吧,让我们逐条看一下:

1.1    Main 方法

基本上和我们写的一样。没有什么特别,没有什么可疑,当然就没有什么好说的!

private static void Main(string[] args)

{

Program.CountAsync();

Console.WriteLine("Async run back
to main");

Console.Read();

}

1.2    CountAsync 方法

这个不是我写的CountAsync 方法么?

我的 async 关键字,我的await Task.Run 都去那儿了?

为了揭开这个谜底,找出真相,真相永远只有一个,我们先看一下方法的构成:

  • 声名一个类型为<CountAsync>d_2 类型的stateMachine 局部变量

  • 为 stateMachine.t_builder 属性赋值

  • 将 stateMachine 的 1_state 字段赋值为-1

  • 调用 stateMachine 的t_builder 成员的 Start 方法。

[DebuggerStepThrough]

[AsyncStateMachine(typeof
(Program.<CountAsync>d__2))]

private static void CountAsync()

{

Program.<CountAsync>d__2
stateMachine;

stateMachine.t__builder =
 AsyncVoidMethodBuilder.Create();

stateMachine.1__state = -1;

stateMachine.t__builder.Start<Program.<CountAsync>d__2>(ref
stateMachine);

}

1.3    Action delegate – Action 委托

private static Action $__CachedAnonymousMethodDelegate1;

生成了一个 Action 委托,巧合的是,我们在调用 Task.Run 方法启动一个新的 Task 时,传的也是一个Action
委托,难道这是一个巧合,还是别有用意?

1.4    Async Method- 异步方法


一个方法,代码示例如下,没啥特别,是个程序猿就能写,但是它是怎么来的那?因为我没有写这个方法。

[CompilerGenerated]

private static void <CountAsync>
b__0()

{

for (int index = 0; index <
10; ++index)

{

Console.WriteLine(index);

Thread.Sleep(100);

}

}

看一下方法体,忽然觉得很眼熟。美女! 我们好像哪里见过。仔细想想,这不就是我们写的Lambda 表达式么,让我们再来看一眼我们的 Lambda
表达式。原来我们传给 TaskRun 的 Action 类型的委托转换成了一个方法。

而且我们也看到了编译器生成了一个 Action 类型委托的字段,估计就是用来传递这个自动生成的方法的。

await Task.Run(() =>

{

for (int i = 0; i < 10; i++)

{

Console.WriteLine(i);

Thread.Sleep(100);

}

}

);

1.5    CountAsync StateMachine – CountAsync 状态机


CountAsync ,这个名字很熟悉,是我声名的异步方法的名字,并且使用了 async 关键字进行了限制 -- private static async
void CountAsync()。

但是我声名的是一个方法,绝不是一个 struct 呀!为什么编译成了 struct 那?

难道是 async 这个东西在作怪,因为在这个代码里再也找不到 async 了,它消失了。不,不是消失了,而是变成一个 Struct,
而且实现了IAsyncStateMachine接口。

private struct <CountAsync>d__2 :
IAsyncStateMachine

那么IAsyncStateMachine 接口是做什么的?百度一下,还是直接去MSDN 英文网站吧,看看 Microsoft 怎么说。

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.iasyncstatemachine(v=vs.110).aspx
上面说,Represents state machines that are generated for asynchronous methods. This
type is intended for compiler use only.

大致意思是(我的英文水平也不咋的,所以每次碰到一些概念呀,我通常比较喜欢读英文,这样说起来比较官方了,其实有点 ZB):
IAsyncStateMachine
代表了一个为异步方法生成的状态机,它是转么为了编译设计的。代码不能调用它。(看到这里我有一个疑问,既然专门设计的为什么要声名为 public)。

晕!还没有弄明白IAsyncStateMachine 时神马,又来个状态机 (State Machine) ,
这又是什么?忽然间觉得天旋地转,原来我只是一个小白,啥也不知道。黑发不知勤学早, 白首方悔读书迟(废话02)。

有事问 Google/Bai du,很多关于状态机的解释是数字电路,恍惚间觉得我好像在大学里读过这门课,但是又好像没读过。(废话03)

不管了,状态机大致上说的是,在有限个状态以及这些状态的转移和动作等行为的模型。每个状态可以分为进入动作,退出动作等。在编程中有基于事件的状态机。事件,难道这个
struct 使用了事件?且行且学习吧!

言归正传,IAsyncStateMachine 接口在System.Runtime.CompilerServices命名空间下,有两个的方法:

  • MoveNext – 从一个状态转移到另外一个状态

  • SetStateMachine  - 配置状态机堆分配的一个拷贝

1.5.1   字段

好了,看了了struct 的声名,看一下它的成员吧,先从字段看起(我们知道 .NET
中的属性实际上是对字段的封装,编译器会对其做相应转化),为了易读我对属性名字做了一些加工,这样让各位看官看起来更舒服吧:

  • Int32 _state – 一个用于表示状态机状态的整数
    • 0 --  方法 CountAsync 异步执行完成

    • -1 – 第一次执行 Async 方法

    • TaskAwaiter _awaiter –设置等待异步任务完成时要执行的操作:

TaskAwaiter, await。 多么的相似呀! 难道它们之间有什么神秘的联系!

按照我八卦的思维来说,这个肯定有不可告人的联系。(废话04)

但是我时程序猿,需要理性的思维,还是看看源代码吧:

  • 嗯,TaskAwaiter 是一个 struct, 实现了 INotifyCompletion(OnCompleted),
    ICrtiticalNotifyCompletion(OnUnsafeCompleted)。从这里可以看出 TaskAwaiter 需要对一个 Task
    执行完成做出处理,或者说对 Thread 的完成事件做相应。在接着看,我们发现,OnCompleted 和 OnUnsafeCompleted 均调用了
    Task 类的SetContinuationForAwait 方法,类似类 Continue 方法,当一个方法完成后执行后续方法。

  • 另外 TaskAwaiter 有一个只读的 Task 属性,在构造函数中初始化。

  • IsCompleted 属性,标识其维护的 Task 是否完成。

  • AsyncVoidMethodBuilder _builder – 用来创建一个异步无返回值的方法。

用反射神器看清一下AsyncVoidMethodBuilder。哦,原来如此!

  • AsyncVoidMethodBuilder 类有一个私有的 Task 属性,看到这里,我瞬间好像明白了什么,原来 async
    方法会偷偷的在后台为你穿件一个 Task, Task 是什么,本质上是一个线程,Thread。

private Task Task

{

get

{

if (this.m_task ==
null)

this.m_task = new Task();

return
this.m_task;

}

}

啊!原来 async 是一个披着漂亮外皮的 Thread。妖怪,哪儿跑!

  • Create() 方法 – 创建一个AsyncVoidMethodBuilder 实例,注意到AsyncVoidMethodBuilder 是一个
    struct 类型,值类型,值类型和引用类型的区别还是很大滴,用一个静态方法创建实例,为一个必要的属性,字段赋值是很到的选择。

public void Start<TStateMachine>(ref TStateMachine
stateMachine) where TStateMachine : IAsyncStateMachine

{

this.m_coreState.Start<TStateMachine>(ref stateMachine);

}

  • AwaitOnCompleted()/AwaitOnUnsafeCompleted – 什么,什么,我没听错吧!Await +
    OnCompleted/OnUnsafeCompleted,难道 await 关键字被转换成了TaskAwaiter
    对象的INotifyCompletion 方法 OnCompleted 的调用。看一下代码,先!有可能,但是还是需要看下去。

public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref
TAwaiter awaiter,

ref TStateMachine stateMachine)

where TAwaiter : INotifyCompletion

where TStateMachine : IAsyncStateMachine

{

try

{

Action
completionAction =
this.m_coreState.GetCompletionAction<AsyncVoidMethodBuilder,
TStateMachine>(ref this, ref stateMachine);

awaiter.OnCompleted(completionAction);

}

catch (Exception ex)

{

AsyncMethodBuilderCore.ThrowAsync(ex, (SynchronizationContext) null);

}

}

  • SetResult  -- 设置值

  • SetException – 设置 Exception

1.5.2   MoveNext 方法


从上面介绍,MoveNext 方法用来处理状态机不同状态之间的转换。根据 state 不同执行不同的操作,实际上是一个 switch(state)
的语句。

复杂的事物往往是由简单的东西构成的,switch 语句,多么熟悉的语句,经历那么多年风和雨!

1.5.3   代码

Agile 说唯一有说服力,且完全正确的东西就是代码。那么就把完全的反编译代码放在下面,大侠们自己阅读,自己体会。

[CompilerGenerated]

[StructLayout(LayoutKind.Auto)]

private struct <CountAsync>d__2 :
IAsyncStateMachine

{

public int 1__state;

public AsyncVoidMethodBuilder
t__builder;

private TaskAwaiter
u__$awaiter3;

private object t__stack;

void
IAsyncStateMachine.MoveNext()

{

try

{

bool flag
= true;

TaskAwaiter awaiter;

switch
(this.1__state)

{

case -3:

goto label_8;

case 0:

awaiter = this.u__$awaiter3;

this.u__$awaiter3 = new TaskAwaiter();

this.1__state = -1;

break;

default:

Console.WriteLine("Async run ");

if (Program.CS$9__CachedAnonymousMethodDelegate1 == null)

{

// ISSUE: method pointer

Program.CS$9__CachedAnonymousMethodDelegate1 = new Action((object) null,
__methodptr(<CountAsync>b__0));

}

awaiter =
Task.Run(Program.CS$9__CachedAnonymousMethodDelegate1).GetAwaiter();

if (!awaiter.IsCompleted)

{

this.1__state = 0;

this.u__$awaiter3 = awaiter;

this.t__builder.AwaitUnsafeOnCompleted<TaskAwaiter,
Program.<CountAsync>d__2>(ref awaiter, ref this);

flag = false;

return;

}

else

break;

}

awaiter.GetResult();

awaiter =
new TaskAwaiter();

Console.WriteLine("Async completed");

}

catch (Exception
ex)

{

this.1__state = -2;

this.t__builder.SetException(ex);

return;

}

label_8:

this.1__state =
-2;

this.t__builder.SetResult();

}

[DebuggerHidden]

void
IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)

{

this.t__builder.SetStateMachine(param0);

}

}

}

}

1.6    执行过程


  1. 程序从 Main 入口开始执行。(常识呀,C 语言就是这么告诉我们的,Hello World 也是这么写出来的)

  2. 调用有 async 标识的方法

  3. 初始化 asycnMethod 编译自动生成的状态机

  4. 调用状态机的 MoveNext 方法
    1. 初始化委托, Action 委托

    2. 使用Task.Run 运行 Action 委托所表示的方法,使用 Task.GetAwaiter 初始化本地的 awaiter

    3. awaiter 对象会受到 OnCompletion 的通知,继续执行其后续代码

    4. 设置异常信息

    5. 设置返回值信息(如果有返回值)

    6. 运行结束

1.7    总结

Async 关键字标识的方法,编译器会把其编译成一个实现了 IAsyncStateMachined 接口的状态机结构体 (struct) 。

状态机允许一个线程执行其MoveNext 中的部分代码并返回,应为有相应的状态对应,这个时候需要调用 SetStateMachine
来维护当前对应的状态值。

Await 关键字修一个返回 Task 或者 Task<TResult> 的方法。当代码遇到 await 后的方法时,创建一个
Task,  并且将控制权交还给 状态机的调用者。Await 关键字实际上创建一个在Task 的ContinueWith,在ContinueWith
中激活状态机,并且获取线程控制权。

Async 与 Await 关键字研究,码迷,mamicode.com

时间: 2024-10-12 15:43:59

Async 与 Await 关键字研究的相关文章

async和await关键字实现异步编程

async和await关键字实现异步编程 异步编程 概念 异步编程核心为异步操作,该操作一旦启动将在一段时间内完成.所谓异步,关键是实现了两点:(1)正在执行的此操作,不会阻塞原来的线程(2)一旦启动的此操作,可以继续执行其他任务.当该操作完成时,将调用回调函数来通知该操作已经结束. [注]:本人一直以为同步和异步都属于多线程的范畴,到今天才明白完全错误,异步和多线程是属于不同范畴,多线程和异步是并发的两种形式,并行处理和线程同步是多线程的两种形式,这是我当前的理解,不知是否有误,文中若有错误,

异步编程,采用WorkgroupWorker,async和await关键字

金科玉律:不要在UI线程上执行耗时的操作:不要在除了UI线程之外的其他线程上访问UI控件! NET1.1的BeginInvoke异步调用,需要准备3个方法:功能方法GetWebsiteLength,结果方法DownloadComplete,呼叫方法BeginInvoke! 但很不幸,在UI线程之外访问UI线程控件!调用失败.线程同步必须在线程所属进程的公共区域保留同步区,以此实现线程间的通讯. 二.C#2.0引入了BackgroundWorker,从而极大的简化了线程间通讯. 三.C#4.0引入

转发:浅谈async、await关键字 =&gt; 深谈async、await关键字

前言 之前写过有关异步的文章,对这方面一直比较弱,感觉还是不太理解,于是会花点时间去好好学习这一块,我们由浅入深,文中若有叙述不稳妥之处,还请批评指正. 话题 (1)是不是将方法用async关键字标识就是异步方法了呢? (2)是不是没有await关键字的存在async就没有存在的意义了呢? (3)用异步方法的条件是什么呢,为什么会有这个条件限制? (4)只能调用.NET Framework内置的用await标识的Task,能否自定义实现呢? (5)在lambda表达式中是否可以用async和aw

可移植类库无法使用async、await关键字

今天遇到了如题所示的问题,平台已经选择了.net 4.5了,可是就是编译不通过,await关键字下出现了红色下划线. 解决方法:安装一个Bcl的补丁包. https://www.nuget.org/packages/Microsoft.Bcl.Async 安装完成后就能编译通过了.

常见的异步方式async 和 await

之前研究过c#的async和await关键字,幕后干了什么,但是不知道为什么找不到相关资料了.现在重新研究一遍,顺便记录下来,方便以后查阅. 基础知识 async 关键字标注一个方法,该方法返回值是一个Task.或者Task<TResult>.void.包含GetAwaiter方法的类型.该方法通常包含一个await表达式.该表达式标注一个点,将被某个异步方法回跳到该点.并且,当前函数执行到该点,将立刻返回控制权给调用方. 以上描述了async方法想干的事情,至于如何实现,这里就不涉猎了. 个

说说C#的async和await 解决卡顿问题 转

C# 5.0中引入了async 和 await.这两个关键字可以让你更方便的写出异步代码. 看个例子: 可以看到,async和await关键字只是把上面的代码变得更简单易懂而已. public class MyClass { public MyClass() { DisplayValue(); //这里不会阻塞 System.Diagnostics.Debug.WriteLine("MyClass() End."); } public Task<double> GetVal

【转】【C#】C# 5.0 新特性——Async和Await使异步编程更简单

一.引言 在之前的C#基础知识系列文章中只介绍了从C#1.0到C#4.0中主要的特性,然而.NET 4.5 的推出,对于C#又有了新特性的增加--就是C#5.0中async和await两个关键字,这两个关键字简化了异步编程,之所以简化了,还是因为编译器给我们做了更多的工作,下面就具体看看编译器到底在背后帮我们做了哪些复杂的工作的. 二.同步代码存在的问题 对于同步的代码,大家肯定都不陌生,因为我们平常写的代码大部分都是同步的,然而同步代码却存在一个很严重的问题,例如我们向一个Web服务器发出一个

Async和Await进行异步编程

使用Async和Await进行异步编程(C#版 适用于VS2015) 你可以使用异步编程来避免你的应用程序的性能瓶颈并且加强总体的响应.然而,用传统的技术来写异步应用是复杂的,同时编写,调试和维护都很困难. VS2012介绍了简单的方法,那就是异步编程,它在.Net Framework 4.5和Windows 运行时提供了异步支持.编译器做了开发者以前做的困难的工作,而且你的应用保持了类似于异步代码的逻辑结构.结果,你轻易地就获得了所有异步编程的优势. 异步提升响应 异步对于可能阻塞的活动是至关

使用 Async 和 Await 的异步编程

来自:http://msdn.microsoft.com/library/vstudio/hh191443 异步对可能起阻止作用的活动(例如,应用程序访问 Web 时)至关重要. 对 Web 资源的访问有时很慢或会延迟. 如果此类活动在同步过程中受阻,则整个应用程序必须等待. 在异步过程中,应用程序可继续执行不依赖 Web 资源的其他工作,直至潜在阻止任务完成. 下表显示了异步编程提高响应能力的典型区域. 从 .NET Framework 4.5 和 Windows 运行时中列出的 API 包含