简介
.net4.5(C#5.0)之后,两个异步语法糖——让异步逻辑成为了近似线性的流程。
基本语法
关键字:方法头使用 async 修饰。
要求:包含 N(N>0) 个 await 表达式(不存在 await 表达式的话 IDE 会发出警告),表示需要异步执行的任务。
返回类型:只能返回 3 种类型(void、Task 和Task<T>)。Task 和Task<T> 标识返回的对象会在将来完成工作,表示调用方法和异步方法可以继续执行。
参数:数量不限,但不能使用 out 和 ref 关键字。
命名约定:方法后缀名应以 Async 结尾。
其它:匿名方法和 Lambda 表达式也可以作为异步对象;async 是一个上下文关键字;关键字 async 必须在返回类型前。
控制流
解释:
事件处理程序调用并等待 AccessTheWebAsync() 异步方法。
AccessTheWebAsync创建 HttpClient 对象并调用它的 GetStringAsync 异步方法来下载网站内容。
假设 GetStringAsync 中发生了某种情况,该情况挂起了它的进程。可能必须等待网站下载或一些其他阻塞的活动。为避免阻塞资源,GetStringAsync() 会将控制权出让给其调用方 AccessTheWebAsync。GetStringAsync 返回 Task,其中 TResult 为字符串,并且 AccessTheWebAsync 将任务分配给 getStringTask 变量在工作完成时接收实际字符串值。
由于尚未等待 getStringTask,因此,AccessTheWebAsync 可以继续执行不依赖于 GetStringAsync 得出最终结果的其他任务。该任务由对同步方法 DoIndependentWork 的调用表示。
DoIndependentWork 是完成其工作并返回其调用方的同步方法。
AccessTheWebAsync 已完成工作,可以不受 getStringTask 的结果影响。 接下来,AccessTheWebAsync 需要计算并返回该下载字符串的长度,但该方法仅在具有字符串时才能计算该值。因此,AccessTheWebAsync 使用一个 await 运算符来挂起其进度,并把控制权交给调用 AccessTheWebAsync 的方法。AccessTheWebAsync 将 Task<int> 返回至调用方。 该任务产生下载字符串长度返回。如果 GetStringAsync(即 getStringTask)在 AccessTheWebAsync 等待前完成,则控制权会保留在 AccessTheWebAsync 中。 如果异步调用过程 (getStringTask) 已完成,并且 AccessTheWebSync 不必等待最终结果,则挂起然后返回到 AccessTheWebAsync,但这会造成成本的浪费。
GetStringAsync 完成并生成一个字符串结果。 字符串结果不是通过你预期的方式调用 GetStringAsync 所返回的。(此方法已在步骤 3 中返回一个任务。)相反,字符串结果存储在表示完成方法 getStringTask 的任务中。 await 运算符从 getStringTask 中检索结果。赋值语句将检索到的结果赋给 urlContents。
当 AccessTheWebAsync 具有字符串结果时,该方法可以计算字符串长度。然后,AccessTheWebAsync 工作也将完成,并且等待事件处理程序可继续使用。
编译流
以一个简单的指控台程序为例
static void Main(string[] args) { new Program().SampleMethodAsync();
Console.WriteLine("THERE");Console.Read();} async void SampleMethodAsync() {
Console.WriteLine(await GetHere()); } Task<string> GetHere() { return Task.Run(() => { Thread.Sleep(1000); return "HERE"; }); }
从ILSPY中我们可以看出async方法中的代码会被编译至
// System.Runtime.CompilerServices.AsyncMethodBuilderCore
[DebuggerStepThrough, SecuritySafeCritical]
internal void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
if (stateMachine == null)
{
throw new ArgumentNullException("stateMachine");
}
Thread currentThread = Thread.CurrentThread;
ExecutionContextSwitcher executionContextSwitcher = default(ExecutionContextSwitcher);
RuntimeHelpers.PrepareConstrainedRegions();
try
{
ExecutionContext.EstablishCopyOnWriteScope(currentThread, false, ref executionContextSwitcher);
stateMachine.MoveNext();
}
finally
{
executionContextSwitcher.Undo(currentThread);
}
}
注:stateMachine就是SampleMethod方法编译时生成的内部结构体
再看MoveNext的具体实现
bool '<>t__doFinallyBodies';
Exception '<>t__ex';
int CS$0$0000;
TaskAwaiter<string> CS$0$0001;
TaskAwaiter<string> CS$0$0002;
try
{
'<>t__doFinallyBodies' = true;
CS$0$0000 = this.'<>1__state';
if (CS$0$0000 != 0)
{
CS$0$0001 = this.'<>4__this'.GetHere().GetAwaiter();
if (!CS$0$0001.IsCompleted)
{
this.'<>1__state' = 0;
this.'<>u__$awaiter1' = CS$0$0001;
this.'<>t__builder'.AwaitUnsafeOnCompleted(ref CS$0$0001, ref this);
'<>t__doFinallyBodies' = false;
return;
}
}
else
{
CS$0$0001 = this.'<>u__$awaiter1';
this.'<>u__$awaiter1' = CS$0$0002;
this.'<>1__state' = -1;
}
Console.WriteLine(CS$0$0001.GetResult());
}
- GetHere()返回Task<string>
- GetAwaiter()返回TaskAwaiter<string>
注:TaskAwaiter的属性,可以看出我们可以对异步方法进一步扩展
public struct TaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion
{
public bool IsCompleted { get; }
public void GetResult();
public void OnCompleted(Action continuation);
public void UnsafeOnCompleted(Action continuation);
}
// System.Runtime.CompilerServices.AsyncVoidMethodBuilder
[__DynamicallyInvokable, SecuritySafeCritical]
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine
{
try
{
Action completionAction = this.m_coreState
.GetCompletionAction<AsyncVoidMethodBuilder, TStateMachine>(ref this, ref stateMachine);
awaiter.UnsafeOnCompleted(completionAction);
}
catch (Exception exception)
{
AsyncMethodBuilderCore.ThrowAsync(exception, null);
}
}
- 创建了一个Action,MoveNext方法的信息已经随着stateMachine被封装进去了。
- 把Action交给Awaiter,让它在await的操作完成后执行这个Action。
// System.Runtime.CompilerServices.AsyncMethodBuilderCore
[SecuritySafeCritical]
internal Action GetCompletionAction<TMethodBuilder, TStateMachine>(ref TMethodBuilder builder, ref TStateMachine stateMachine)
where TMethodBuilder : IAsyncMethodBuilder
where TStateMachine : IAsyncStateMachine
{
Debugger.NotifyOfCrossThreadDependency();
ExecutionContext executionContext = ExecutionContext.FastCapture();
Action action;
AsyncMethodBuilderCore.MoveNextRunner moveNextRunner;
if (executionContext != null && executionContext.IsPreAllocatedDefault)
{
action = this.m_defaultContextAction;
if (action != null)
{
return action;
}
moveNextRunner = new AsyncMethodBuilderCore.MoveNextRunner(executionContext);
action = new Action(moveNextRunner.Run);
if (AsyncCausalityTracer.LoggingOn)
{
action = (this.m_defaultContextAction = this.OutputAsyncCausalityEvents<TMethodBuilder>(ref builder, action));
}
else
{
this.m_defaultContextAction = action;
}
}
else
{
moveNextRunner = new AsyncMethodBuilderCore.MoveNextRunner(executionContext);
action = new Action(moveNextRunner.Run);
if (AsyncCausalityTracer.LoggingOn)
{
action = this.OutputAsyncCausalityEvents<TMethodBuilder>(ref builder, action);
}
}
if (this.m_stateMachine == null)
{
builder.PreBoxInitialization<TStateMachine>(ref stateMachine);
this.m_stateMachine = stateMachine;
this.m_stateMachine.SetStateMachine(this.m_stateMachine);
}
moveNextRunner.m_stateMachine = this.m_stateMachine;
return action;
}
- MoveNextRunner的Run方法被封装到了返回的Action里。
- Run方法里面有两条分支:
1.直接调用MoveNext
2.通过InvokeMoveNext调用MoveNext
执行action
// System.Threading.Tasks.Task
[SecurityCritical]
internal void SetContinuationForAwait(
Action continuationAction,
bool continueOnCapturedContext,
bool flowExecutionContext,
ref StackCrawlMark stackMark)
{
TaskContinuation taskContinuation = null;
if (continueOnCapturedContext)
{
SynchronizationContext currentNoFlow = SynchronizationContext.CurrentNoFlow;
if (currentNoFlow != null && currentNoFlow.GetType() != typeof(SynchronizationContext))
{
taskContinuation = new SynchronizationContextAwaitTaskContinuation(
currentNoFlow, continuationAction, flowExecutionContext, ref stackMark);
}
else
{
TaskScheduler internalCurrent = TaskScheduler.InternalCurrent;
if (internalCurrent != null && internalCurrent != TaskScheduler.Default)
{
taskContinuation = new TaskSchedulerAwaitTaskContinuation(
internalCurrent, continuationAction, flowExecutionContext, ref stackMark);
}
}
}
if (taskContinuation == null && flowExecutionContext)
{
taskContinuation = new AwaitTaskContinuation(continuationAction, true, ref stackMark);
}
if (taskContinuation != null)
{
if (!this.AddTaskContinuation(taskContinuation, false))
{
taskContinuation.Run(this, false);
return;
}
}
else if (!this.AddTaskContinuation(continuationAction, false))
{
AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);
}
}
// System.Threading.Tasks.AwaitTaskContinuation
[SecurityCritical]
internal static void UnsafeScheduleAction(Action action, Task task)
{
AwaitTaskContinuation awaitTaskContinuation = new AwaitTaskContinuation(action, false);
TplEtwProvider log = TplEtwProvider.Log;
if (log.IsEnabled() && task != null)
{
awaitTaskContinuation.m_continuationId = Task.NewId();
log.AwaitTaskContinuationScheduled(
(task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id,
task.Id,
awaitTaskContinuation.m_continuationId);
}
ThreadPool.UnsafeQueueCustomWorkItem(awaitTaskContinuation, false);
}
// System.Threading.ThreadPool
[SecurityCritical]
internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal)
{
ThreadPool.EnsureVMInitialized();
try
{
}
finally
{
ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
}
}
QueueUserWorkItem方法内部调用了ThreadPoolGlobals.workQueue.Enqueue——异步的方法就是在这里被执行。
再来看一下一种情况
async void SampleMethod()
{
Console.WriteLine("WHERE");
Console.WriteLine(await GetHere());
}
此时的MoveNext
try
{
'<>t__doFinallyBodies' = true;
CS$0$0000 = this.'<>1__state';
if (CS$0$0000 != 0)
{
Console.WriteLine("WHERE");
CS$0$0001 = this.'<>4__this'.GetHere().GetAwaiter();
if (!CS$0$0001.IsCompleted)
{
this.'<>1__state' = 0;
this.'<>u__$awaiter1' = CS$0$0001;
this.'<>t__builder'.AwaitUnsafeOnCompleted(ref CS$0$0001, ref this);
'<>t__doFinallyBodies' = false;
return;
}
}
else
{
CS$0$0001 = this.'<>u__$awaiter1';
this.'<>u__$awaiter1' = CS$0$0002;
this.'<>1__state' = -1;
}
Console.WriteLine(CS$0$0001.GetResult());
}
可以看出来await前后的代码被放到了两个区块里,而这两个区块,也就是MoveNext的两个分支
总结
- async方法中的代码会被移交给IAsyncStateMachine(状态机)的MoveNext方法。
async方法中await操作前后的代码被分离,无论方法是同步还是异步都可以用async关键字来进行标识,因为用async标识只是显示表明在该方法内可能会用到await关键字使其变为异步方法,而且将该异步方法进行了明确的划分,只有用了await关键字时才是异步操作,其余一并为同步操作
- 主线程直接执行await前的代码(同步),并将await的Task移交给线程池ThreadPoolGlobal(异步)。
- 子线程执行完主线程递交来的Task后,再次走入MoveNext方法,执行await后的代码。(同步或异步)