理解async特性

能够从两方面来理解async特性:web

  • 从语言特性上讲,它定义了一种行为。
  • 从编译上讲,它是一个语法糖。

1. 运行到await时,async方法的行为

1.1 休眠和恢复方法

当程序运行到await关键字时,发生了两件事:异步

  • 运行代码的线程将会被释放。从普通方法或同步代码的角度看,就是方法返回了。
  • 当await的Task完成时,方法将会继续运行,好像历来没有返回过同样。
    这个过程就像计算机睡眠(S4 sleep)同样,方法的当前状态被存入硬盘,而后彻底退出,一点内存资源都不会占用。

    一个阻塞的方法就像是计算机休眠(S3 sleep)同样,它占用不多的资源,本质上它还在运行。async

1.2 记录方法的状态

首先,async方法中的全部本地变量都会被记录下来:方法参数、定义在做用域内的任何变量、其它变量(好比循环计数)、若是不是static方法的话还有this变量。全部这些变量都被做为一个object存储在托管堆上。
其次,C#须要记录方法目前到达那个await了,可能使用一个数字来表示。
另外,相似下面的大型表达式,须要一个栈来存储子表达式的返回值:性能

 
 
 
 
int myNum = await MethodAsync(await myTask, await Method2Async());

最后,await表达式返回的Task也须要存储。优化

1.3 获取上下文

C#会在await时获取各类上下文,而且在方法继续时恢复上下文。
最重要的上下文是同步上下文(synchronization context),对于UI应用程序尤为重要。
调用上下文(CallContext),存储逻辑线程生命周期内的数据。使用在程序中使用这个上下文是一个糟糕的实践,虽然它能够减小方法的参数。这个上下文在异步环境下没有用,由于方法可能在一个彻底不一样的线程上恢复。ui

1.4 await不能使用的状况

await能够在标记为async的方法的大多数位置使用,可是有一些例外:this

1.catch和finally代码块,

会使异常难以定义。spa

 
 
 
 
//非法代码:try{ page = await webClient.DownloadStringTaskAsync("http://oreilly.com");}catch (WebException){ page = await webClient.DownloadStringTaskAsync("http://oreillymirror.com");}//替代的合法写法:bool failed = false;try{ page = await webClient.DownloadStringTaskAsync("http://oreilly.com");}catch (WebException){ failed = true;}if (failed){ page = await webClient.DownloadStringTaskAsync("http://oreillymirror.com");}

2.lock代码块

lock是为了防止在同一时刻不一样的线程访问同一个对象。可是由于异步代码会释放线程,而后在不肯定的时间以后恢复到可能不一样的线程,这样一来在await过程当中维护一个lock就彻底没有必要了。线程

  • 若是须要锁住的资源并非必须异步的,能够在await先后显式地使用两次lock:
 
 
 
 
lock (sync){ // 准备调用异步方法}int myNumber = await MethodAsync();lock (sync){ // 使用异步方法的返回值}
  • 若是确实须要在异步操做中维护一些lock,那么很不幸,这很容易形成死锁。最好考虑从新设计代码结构。

3.LINQ 查询语句

在查询语句中使用await大多数状况下是非法的。由于,LINQ会被编译器编译器编译成Lambda表达式。Lambda表达式须要被标记为async。可是编译器并不会隐式地标记Lambda表达式为async。
解决方案是,将LINQ查询语句写为等价的扩展方法调用,此时能够显示地标记Lambda为async。设计

 
 
 
 
IEnumberable<Task<int>> tasks = myInts .Where(x => x != 9) .Select(async x => await DoSomethingAsync(x) + await DoSomethingElseAsync(x));

4.unsafe代码块

unsafe代码应该保持独立,它不须要是异步的。await关键的编译会破坏unsaf代码。

1.5 async方法只有在须要时才是异步的

async方法在到达第一个await才会暂停,可是这不是必定的。有时在到达第一个await时,task已经执行完成了。下面状况下,Task可能已经完成:

  • 建立时就完成了。
  • 从一个没有执行await的async方法返回的Task。
  • 异步操做确实已经完成了。(可能由于在await以前,线程忙于其它工做)
  • 从一个执行到await的async方法返回,可是这个方法中await的Task也已经完成。(此时,整个异步方法链是同步的)

2. async的编译过程

下面从一个简单的async方法来解释编译的过程:

 
 
 
 
public async Task<int> Method1(){ int foo = 6; await Task.Delay(500); return foo;}

2.1 存根方法

编译器首先会将async方法替换为一个存根方法(stub Method)。

 
 
 
 
public Task<int> Mehtod(){ <Method>d_0 stateMachine = new <Method>d_0() stateMachine.<>_this = this; stateMachine.<>t_builder = AsyncTaskMethodBuilder<int>.Create(); stateMachine.<>_state = -1; stateMachine.t_builder.Start<<Method>d_0>(ref stateMachine); return stateMachine.<>t_builder.Task;}

存根方法的大多数工做就是初始化一个结构体的变量(<Method>d_0)。这是一个状态机。存根方法调用Start方法,而后返回一个Task。

2.2 状态机结构体

这个状态机选择使用一个struct而不是class,主要是出于性能方面的考虑(若是异步方法是同步完成的,那么就无需在堆上分配空间了)。

 
 
 
 
public struct <Method>d_0{ ... public int <>1_state; //标记执行到第几个await,-1表示未执行 public int <foo>5_1; //保存原方法中的foo变量值 public MyClass <>4_this; //实例方法,保存this变量,静态方法无此项 public AsyncTaskMethodBuilder<int> <>t_builder //状态机共享逻辑的Helper,与TaskCompleteSource相似,区别是它会优化异步方法,而且是一个struct不是class private object <>t_stack //用于大型表达式中的await。 private TaskAwaiter <>u_$awaiter2; //临时存储,Task完成时帮助通知完成。 ...}

2.3 MoveNext方法

MoveNext方法是状态机必须的方法,它在第一次运行时和从await继续运行时被调用。该方法须要进行下面的编译步骤:

1.将原方法拷贝到MoveNext方法:

 
 
 
 
<foo>5_1 = 3;Task t = Task.Delay(500);//await继续的逻辑代码return <foo>5_1;

2.转换完成时的返回值

源代码中的每个返回语句都须要转换。

 
 
 
 
<>t_builder.SetResult(<foo>5_1);//设置值return; //MoveNext返回void

3.跳转到正确的位置

生成的中间代码相似下面的switch语句:

 
 
 
 
switch(<>1_state){ case -1: //第一次调用时 <foo>5_1 = 3; Task t = Task.Delay(500); //await继续的逻辑代码 case 0: //第一个await <>t_builder.SetResult(<foo>5_1); return;}

4.运行到await时暂停方法

在Task完成时,须要更新状态。

 
 
 
 
switch(<>1_state){ case -1: //第一次调用时 <foo>5_1 = 3; //************** u_&awaiter2 = Task.Delay(500).GetAwaiter(); //await继续的逻辑代码 <>1_state = 0; <>t_builder.AwaitUnsafeOnCompleted(<>u_$awaiter2, this); return; //************** case 0: //第一个await <>t_builder.SetResult(<foo>5_1); return;}

这个过程当中还包括更复杂的过程,好比获取同步上下文等等。

5.await以后继续运行

 
 
 
 
switch(<>1_state){ case -1: //第一次调用时 <foo>5_1 = 3; u_&awaiter2 = Task.Delay(500).GetAwaiter(); //await继续的逻辑代码 <>1_state = 0; <>t_builder.AwaitUnsafeOnCompleted(<>u_$awaiter2, this); return; case 0: //第一个await //************** <>u_$awaiter2.GetResult(); //await返回后,获取返回值 //************** <>t_builder.SetResult(<foo>5_1); return;}

6.同步完成

若是await以前,Task已经完成运行,那么无需暂停,直接goto:

 
 
 
 
switch(<>1_state){ case -1: //第一次调用时 <foo>5_1 = 3; //Task t = Task.Delay(500); u_&awaiter2 = Task.Delay(500).GetAwaiter(); //若是同步执行,直接goto,无需站厅代码 if(<>u_$awaiter2.IsCompleted) { goto case 0; } <>1_state = 0; <>t_builder.AwaitUnsafeOnCompleted(<>u_$awaiter2, this); return; case 0: //第一个await <>u_$awaiter2.GetResult(); //await返回后,获取返回值 <>t_builder.SetResult(<foo>5_1); return;}

7.捕获异常

若是在async方法运行期间抛出了异常,可是没有try…catch代码来处理异常,编译器生成的代码会捕获这个异常,而后设置返回的Task为faulted。



相关文章
相关标签/搜索