为了更容易理解这个问题,我们举一个简单的例子:用异步的方式在控制台上分两步输出“Hello World!”,我这边使用的是Framework 4.5.2
Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Let's Go!");
await TestAsync();
Console.Write(" World!");
}
static Task TestAsync()
{
return Task.Run(() =>
{
Console.Write("Hello");
});
}
}
探究反编译后的源码
接下来我们使用 .NET reflector (也可使用 dnSpy 等) 反编译一下程序集,然后一步一步来探究 async await 内部的奥秘。
Main方法
DebuggerStepThrough]
private static void <Main>(string[] args)
{
Main(args).GetAwaiter().GetResult();
}
[AsyncStateMachine(typeof(<Main>d__0)), DebuggerStepThrough]
private static Task Main(string[] args)
{
<Main>d__0 stateMachine = new <Main>d__0
{
<>t__builder = AsyncTaskMethodBuilder.Create(),
args = args,
<>1__state = -1
};
stateMachine.<>t__builder.Start<<Main>d__0>(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
卧槽!竟然有两个 Main 方法:一个同步、一个异步。原来,虽然我们写代码时为了在 Main 方法中方便异步等待,将 void Main 改写成了async Task Main,但是实际上程序入口仍是我们熟悉的那个 void Main。
另外,我们可以看到异步 Main 方法被标注了AsyncStateMachine
特性,这是因为在我们的源代码中,该方法带有修饰符async
,表示该方法是一个异步方法。
好,我们先看一下异步Main方法内部实现,它主要做了三件事:
- 首先,创建了一个类型为
<Main>d__0
的状态机 stateMachine,并初始化了公共变量 <>t__builder、args、<>1__state = -1
- <>t__builder:负责异步相关的操作,是实现异步 Main 方法异步的核心
- <>1__state:状态机的当前状态
- 然后,调用
Start
方法,借助 stateMachine, 来执行我们在异步 Main 方法中写的代码
- 最后,将指示异步 Main 方法运行状态的
Task
对象返回出去
Start
首先,我们先来看一下Start
的内部实现
// 所属结构体:AsyncTaskMethodBuilder
[SecuritySafeCritical, DebuggerStepThrough, __DynamicallyInvokable]
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine: IAsyncStateMachine
{
if (((TStateMachine) stateMachine) == null)
{
throw new ArgumentNullException("stateMachine");
}
ExecutionContextSwitcher ecsw = new ExecutionContextSwitcher();
RuntimeHelpers.PrepareConstrainedRegions();
try
{
ExecutionContext.EstablishCopyOnWriteScope(ref ecsw);
我猜,你只能看懂stateMachine.MoveNext()
,对不对?好,那我们就来看看这个状态机类<Main>d__0
,并且着重看它的方法MoveNext
。
MoveNext
[CompilerGenerated]
private sealed class <Main>d__0 : IAsyncStateMachine
{

先简单理一下内部逻辑:
- 设置变量 num = -1,此时 num != 0,则会进入第一个if语句,
- 首先,执行
Console.WriteLine("Let's Go!")
- 然后,调用异步方法
TestAsync
,TestAsync
方法会在另一个线程池线程中执行,并获取指示该方法运行状态的 awaiter
- 如果此时
TestAsync
方法已执行完毕,则像没有异步一般:
- 继续执行接下来的
Console.Write(" World!")
- 最后设置 <>1__state = -2,并设置异步 Main 方法的返回结果
- 如果此时
TestAsync
方法未执行完毕,则:
- 设置 <>1__state = num = 0
- 调用
AwaitUnsafeOnCompleted
方法,用于配置当TestAsync
方法完成时的延续,即Console.Write(" World!")
- 返回指示异步 Main 方法执行状态的 Task 对象,由于同步 Main 方法中通过使用
GetResult()
同步阻塞主线程等待任务结束,所以不会释放主线程(废话,如果释放了程序就退出了)。不过对于其他子线程,一般会释放该线程
大部分逻辑我们都可以很容易的理解,唯一需要深入研究的就是AwaitUnsafeOnCompleted
,那我们接下来就看看它的内部实现
AwaitUnsafeOnCompleted
// 所属结构体:AsyncTaskMethodBuilder
[__DynamicallyInvokable]
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter: ICriticalNotifyCompletion where TStateMachine: IAsyncStateMachine
{
this.m_builder.AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref awaiter, ref stateMachine);
}
咱们一步一步来,先看一下GetCompletionAction
的实现:
// 所属结构体:AsyncMethodBuilderCore
[SecuritySafeCritical]
internal Action GetCompletionAction(Task taskForTracing, ref MoveNextRunner runnerToInitialize)
{
Action defaultContextAction;
MoveNextRunner runner;
Debugger.NotifyOfCrossThreadDependency();
发现一个熟悉的家伙——ExecutionContext,它是用来给咱们延续方法(即Console.Write(" World!");
)提供运行环境的,注意这里用的是FastCapture()
,该内部方法并未捕获SynchronizationContext
,因为不需要流动它。什么?你说你不认识它?大眼瞪小眼?那你应该好好看看《理解C#中的ExecutionContext vs SynchronizationContext》了
接着来到new MoveNextRunner(context, this.m_stateMachine)
,这里初始化了 runner,我们看看构造函数中做了什么:
SecurityCritical]
internal MoveNextRunner(ExecutionContext context, IAsyncStateMachine stateMachine)
{
往下来到defaultContextAction = new Action(runner.Run)
,你可以发现,最终咱们返回的就是这个 defaultContextAction ,所以这个runner.Run
至关重要,不过别着急,我们等用到它的时候我们再来看其内部实现。
最后,回到AwaitUnsafeOnCompleted
方法,继续往下走。构建指示异步 Main 方法执行状态的 Task 对象,设置当前的状态机后,来到awaiter.UnsafeOnCompleted(completionAction);
,要记住,入参 completionAction 就是刚才返回的runner.Run
:
// 所属结构体:TaskAwaiter
[SecurityCritical, __DynamicallyInvokable]
public void UnsafeOnCompleted(Action continuation)
{
OnCompletedInternal(this.m_task, continuation, true, false);
}
[MethodImpl(MethodImplOptions.NoInlining), SecurityCritical]
internal static void OnCompletedInternal(Task task, Action continuation, bool continueOnCapturedContext, bool flowExecutionContext)
{
if (continuation == null)
{
throw new ArgumentNullException("continuation");
}
StackCrawlMark lookForMyCaller = StackCrawlMark.LookForMyCaller;
if (TplEtwProvider.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
{
continuation = OutputWaitEtwEvents(task, continuation);
}
直接来到代码最后一行,看到延续方法的配置
// 所属类:Task
[SecurityCritical]
internal void SetContinuationForAwait(Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext, ref StackCrawlMark stackMark)
{
TaskContinuation tc = null;
if (continueOnCapturedContext)
{
对于我们的示例来说,既没有自定义 SynchronizationContext,也没有自定义 TaskScheduler,所以会直接来到最后一个else if (...)
,重点在于this.AddTaskContinuation(continuationAction, false)
,这个方法会将我们的延续方法添加到 Task 中,以便于当 TestAsync 方法执行完毕时,执行 runner.Run
runner.Run
好,是时候让我们看看 runner.Run 的内部实现了:
SecuritySafeCritical]
internal void Run()
{
if (this.m_context != null)
{
try
{
来到ExecutionContext.Run(this.m_context, callback, this.m_stateMachine, true);
,这里的 callback 是InvokeMoveNext
方法。所以,当TestAsync
执行完毕后,就会执行延续方法 runner.Run,也就会执行stateMachine.MoveNext()
促使状态机继续进行状态流转,这样逻辑就打通了:
private void MoveNext()
{
至此,整个异步方法的执行就结束了,通过一张图总结一下:

最后,我们看一下各个线程的状态,看看和你的推理是否一致(如果有不清楚的联系我,我会通过文字补充):

多个 async await 嵌套
理解了async await的简单使用,那你可曾想过,如果有多个 async await 嵌套,那会出现什么情况呢?接下来就改造一下我们的例子,来研究研究:
)
{
return Task.Run(async () =>
{
反编译之后的代码,上面已经讲解的我就不再重复贴了,主要看看TestAsync()
就行了:
private static Task TestAsync() =>
Task.Run(delegate {
<>c.<<TestAsync>b__1_0>d stateMachine = new <>c.<<TestAsync>b__1_0>d {
<>t__builder = AsyncTaskMethodBuilder.Create(),
<>4__this = this,
<>1__state = -1
};
stateMachine.<>t__builder.Start<<>c.<<TestAsync>b__1_0>d>(ref stateMachine);
return stateMachine.<>t__builder.Task;
});
哦!原来,async await 的嵌套也就是状态机的嵌套,相信你通过上面的状态机状态流转,也能够梳理除真正的执行逻辑,那我们就只看一下线程状态吧:

这也印证了我上面所说的:当子线程完成执行任务时,会被释放,或回到线程池供其他线程使用。
多个 async await 在同一方法中顺序执行
又可曾想过,如果有多个 async await 在同一方法中顺序执行,又会是何种景象呢?同样,先来个例子:
string[] args)
{
Console.WriteLine("Let's Go!");
await Test1Async();
await Test2Async();
Console.Write(" World!");
}
static Task Test1Async()
{
return Task.Run(() =>
{
Console.Write("Say: ");
});
}
static Task Test2Async()
{
return Task.Run(() =>
{
Console.Write("Hello");
});
}
直接看状态机:
[CompilerGenerated]
private sealed class <Main>d__0 : IAsyncStateMachine
{
可见,就是一个状态机状态一直流转就完事了。我们就看看线程状态吧:

WPF中使用 async await
上面我们都是通过控制台举的例子,这是没有任何SynchronizationContext
的,但是WPF(Winform同理)就不同了,在UI线程中,它拥有属于自己的DispatcherSynchronizationContext
。
object sender, RoutedEventArgs e)
{
通过使用DispatcherSynchronizationContext
执行延续方法,又回到了 UI 线程中

__EOF__