能够从两方面来理解async特性:web
当程序运行到await关键字时,发生了两件事:异步
一个阻塞的方法就像是计算机休眠(S3 sleep)同样,它占用不多的资源,本质上它还在运行。async
首先,async方法中的全部本地变量都会被记录下来:方法参数、定义在做用域内的任何变量、其它变量(好比循环计数)、若是不是static方法的话还有this变量。全部这些变量都被做为一个object存储在托管堆上。
其次,C#须要记录方法目前到达那个await了,可能使用一个数字来表示。
另外,相似下面的大型表达式,须要一个栈来存储子表达式的返回值:性能
int myNum = await MethodAsync(await myTask, await Method2Async());
最后,await表达式返回的Task也须要存储。优化
C#会在await时获取各类上下文,而且在方法继续时恢复上下文。
最重要的上下文是同步上下文(synchronization context),对于UI应用程序尤为重要。
调用上下文(CallContext),存储逻辑线程生命周期内的数据。使用在程序中使用这个上下文是一个糟糕的实践,虽然它能够减小方法的参数。这个上下文在异步环境下没有用,由于方法可能在一个彻底不一样的线程上恢复。ui
await能够在标记为async的方法的大多数位置使用,可是有一些例外:this
会使异常难以定义。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");}
lock是为了防止在同一时刻不一样的线程访问同一个对象。可是由于异步代码会释放线程,而后在不肯定的时间以后恢复到可能不一样的线程,这样一来在await过程当中维护一个lock就彻底没有必要了。线程
lock (sync){ // 准备调用异步方法}int myNumber = await MethodAsync();lock (sync){ // 使用异步方法的返回值}
在查询语句中使用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));
unsafe代码应该保持独立,它不须要是异步的。await关键的编译会破坏unsaf代码。
async方法在到达第一个await才会暂停,可是这不是必定的。有时在到达第一个await时,task已经执行完成了。下面状况下,Task可能已经完成:
下面从一个简单的async方法来解释编译的过程:
public async Task<int> Method1(){ int foo = 6; await Task.Delay(500); return foo;}
编译器首先会将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。
这个状态机选择使用一个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完成时帮助通知完成。 ...}
MoveNext方法是状态机必须的方法,它在第一次运行时和从await继续运行时被调用。该方法须要进行下面的编译步骤:
<foo>5_1 = 3;Task t = Task.Delay(500);//await继续的逻辑代码return <foo>5_1;
源代码中的每个返回语句都须要转换。
<>t_builder.SetResult(<foo>5_1);//设置值return; //MoveNext返回void
生成的中间代码相似下面的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;}
在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;}
这个过程当中还包括更复杂的过程,好比获取同步上下文等等。
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;}
若是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;}
若是在async方法运行期间抛出了异常,可是没有try…catch代码来处理异常,编译器生成的代码会捕获这个异常,而后设置返回的Task为faulted。