一个老掉牙的话题,园子里的相关优秀文章已经有不少了,我写这篇文章彻底是想以本身的思惟方式来谈一谈本身的理解。(PS:文中涉及到了大量反编译源码,须要静下心来细细品味)html
为了更容易理解这个问题,咱们举一个简单的例子:用异步的方式在控制台上分两步输出“Hello World!”,我这边使用的是Framework 4.5.2框架
class 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 内部的奥秘。异步
[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; } // 实现了 IAsyncStateMachine 接口 [CompilerGenerated] private sealed class <Main>d__0 : IAsyncStateMachine { // Fields public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; public string[] args; private TaskAwaiter <>u__1; // Methods private void MoveNext() { } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } }
卧槽!居然有两个 Main 方法:一个同步、一个异步。原来,虽然咱们写代码时为了在 Main 方法中方便异步等待,将 void Main 改写成了async Task Main,可是实际上程序入口还是咱们熟悉的那个 void Main。async
另外,咱们能够看到异步 Main 方法被标注了AsyncStateMachine
特性,这是由于在咱们的源代码中,该方法带有修饰符async
,表示该方法是一个异步方法。ide
好,咱们先看一下异步Main方法内部实现,它主要作了三件事:函数
<Main>d__0
的状态机 stateMachine,并初始化了公共变量 <>t__builder、args、<>1__state = -1
Start
方法,借助 stateMachine, 来执行咱们在异步 Main 方法中写的代码Task
对象返回出去首先,咱们先来看一下Start
的内部实现ui
// 所属结构体: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(); } finally { ecsw.Undo(); } }
我猜,你只能看懂stateMachine.MoveNext()
,对不对?好,那咱们就来看看这个状态机类<Main>d__0
,而且着重看它的方法MoveNext
。this
[CompilerGenerated] private sealed class <Main>d__0 : IAsyncStateMachine { // Fields public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; public string[] args; private TaskAwaiter <>u__1; // Methods private void MoveNext() { // 在 Main 方法中,咱们初始化 <>1__state = -1,因此此时 num = -1 int num = this.<>1__state; try { TaskAwaiter awaiter; if (num != 0) { Console.WriteLine("Let's Go!"); // 调用 TestAsync(),获取 awaiter,用于后续监控 TestAsync() 运行状态 awaiter = Program.TestAsync().GetAwaiter(); // 通常来讲,异步任务不会很快就完成,因此大多数状况下都会进入该分支 if (!awaiter.IsCompleted) { // 状态机状态从 -1 流转为 0 this.<>1__state = num = 0; this.<>u__1 = awaiter; Program.<Main>d__0 stateMachine = this; // 配置 TestAsync() 完成后的延续 this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Main>d__0>(ref awaiter, ref stateMachine); return; } } else { awaiter = this.<>u__1; this.<>u__1 = new TaskAwaiter(); this.<>1__state = num = -1; } awaiter.GetResult(); Console.Write(" World!"); } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } this.<>1__state = -2; this.<>t__builder.SetResult(); } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } }
先简单理一下内部逻辑:spa
Console.WriteLine("Let's Go!")
TestAsync
,TestAsync
方法会在另外一个线程池线程中执行,并获取指示该方法运行状态的 awaiterTestAsync
方法已执行完毕,则像没有异步通常:
Console.Write(" World!")
TestAsync
方法未执行完毕,则:
AwaitUnsafeOnCompleted
方法,用于配置当TestAsync
方法完成时的延续,即Console.Write(" World!")
GetResult()
同步阻塞主线程等待任务结束,因此不会释放主线程(废话,若是释放了程序就退出了)。不过对于其余子线程,通常会释放该线程大部分逻辑咱们均可以很容易的理解,惟一须要深刻研究的就是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); } // 所属结构体:AsyncTaskMethodBuilder<TResult> [SecuritySafeCritical, __DynamicallyInvokable] public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter: ICriticalNotifyCompletion where TStateMachine: IAsyncStateMachine { try { // 用于流转状态机状态的 runner AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null; Action completionAction = this.m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize); if (this.m_coreState.m_stateMachine == null) { // 此处构建指示异步 Main 方法执行状态的 Task 对象 Task<TResult> builtTask = this.Task; this.m_coreState.PostBoxInitialization((TStateMachine) stateMachine, runnerToInitialize, builtTask); } awaiter.UnsafeOnCompleted(completionAction); } catch (Exception exception) { AsyncMethodBuilderCore.ThrowAsync(exception, null); } }
我们一步一步来,先看一下GetCompletionAction
的实现:
// 所属结构体:AsyncMethodBuilderCore [SecuritySafeCritical] internal Action GetCompletionAction(Task taskForTracing, ref MoveNextRunner runnerToInitialize) { Action defaultContextAction; MoveNextRunner runner; Debugger.NotifyOfCrossThreadDependency(); // ExecutionContext context = ExecutionContext.FastCapture(); if ((context != null) && context.IsPreAllocatedDefault) { defaultContextAction = this.m_defaultContextAction; if (defaultContextAction != null) { return defaultContextAction; } // 构建 runner runner = new MoveNextRunner(context, this.m_stateMachine); // 返回值 defaultContextAction = new Action(runner.Run); if (taskForTracing != null) { this.m_defaultContextAction = defaultContextAction = this.OutputAsyncCausalityEvents(taskForTracing, defaultContextAction); } else { this.m_defaultContextAction = defaultContextAction; } } else { runner = new MoveNextRunner(context, this.m_stateMachine); defaultContextAction = new Action(runner.Run); if (taskForTracing != null) { defaultContextAction = this.OutputAsyncCausalityEvents(taskForTracing, defaultContextAction); } } if (this.m_stateMachine == null) { runnerToInitialize = runner; } return defaultContextAction; }
发现一个熟悉的家伙——ExecutionContext,它是用来给我们延续方法(即Console.Write(" World!");
)提供运行环境的,注意这里用的是FastCapture()
,该内部方法并未捕获SynchronizationContext
,由于不须要流动它。什么?你说你不认识它?大眼瞪小眼?那你应该好好看看《理解C#中的ExecutionContext vs SynchronizationContext》了
接着来到new MoveNextRunner(context, this.m_stateMachine)
,这里初始化了 runner,咱们看看构造函数中作了什么:
[SecurityCritical] internal MoveNextRunner(ExecutionContext context, IAsyncStateMachine stateMachine) { // 将 ExecutionContext 保存了下来 this.m_context = context; // 将 stateMachine 保存了下来(不过此时为 null) this.m_stateMachine = 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.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext, ref lookForMyCaller); }
直接来到代码最后一行,看到延续方法的配置
// 所属类:Task [SecurityCritical] internal void SetContinuationForAwait(Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext, ref StackCrawlMark stackMark) { TaskContinuation tc = null; if (continueOnCapturedContext) { // 这里咱们用的是不进行流动的 SynchronizationContext SynchronizationContext currentNoFlow = SynchronizationContext.CurrentNoFlow; // 像 Winform、WPF 这种框架,实现了自定义的 SynchronizationContext, // 因此在 Winform、WPF 的 UI线程中进行异步等待时,通常 currentNoFlow 不会为 null if ((currentNoFlow != null) && (currentNoFlow.GetType() != typeof(SynchronizationContext))) { // 若是有 currentNoFlow,那么我就用它来执行延续方法 tc = new SynchronizationContextAwaitTaskContinuation(currentNoFlow, continuationAction, flowExecutionContext, ref stackMark); } else { TaskScheduler internalCurrent = TaskScheduler.InternalCurrent; if ((internalCurrent != null) && (internalCurrent != TaskScheduler.Default)) { tc = new TaskSchedulerAwaitTaskContinuation(internalCurrent, continuationAction, flowExecutionContext, ref stackMark); } } } if ((tc == null) & flowExecutionContext) { tc = new AwaitTaskContinuation(continuationAction, true, ref stackMark); } if (tc != null) { if (!this.AddTaskContinuation(tc, false)) { tc.Run(this, false); } } // 这里会将 continuationAction 设置为 awaiter 中 task 对象的延续方法,因此当 TestAsync() 完成时,就会执行 runner.Run else if (!this.AddTaskContinuation(continuationAction, false)) { AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this); } }
对于咱们的示例来讲,既没有自定义 SynchronizationContext,也没有自定义 TaskScheduler,因此会直接来到最后一个else if (...)
,重点在于this.AddTaskContinuation(continuationAction, false)
,这个方法会将咱们的延续方法添加到 Task 中,以便于当 TestAsync 方法执行完毕时,执行 runner.Run
好,是时候让咱们看看 runner.Run 的内部实现了:
[SecuritySafeCritical] internal void Run() { if (this.m_context != null) { try { // 咱们并未给 s_invokeMoveNext 赋值,因此 callback == null ContextCallback callback = s_invokeMoveNext; if (callback == null) { // 将回调设置为下方的 InvokeMoveNext 方法 s_invokeMoveNext = callback = new ContextCallback(AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext); } ExecutionContext.Run(this.m_context, callback, this.m_stateMachine, true); return; } finally { this.m_context.Dispose(); } } this.m_stateMachine.MoveNext(); } [SecurityCritical] private static void InvokeMoveNext(object stateMachine) { ((IAsyncStateMachine) stateMachine).MoveNext(); }
来到ExecutionContext.Run(this.m_context, callback, this.m_stateMachine, true);
,这里的 callback 是InvokeMoveNext
方法。因此,当TestAsync
执行完毕后,就会执行延续方法 runner.Run,也就会执行stateMachine.MoveNext()
促使状态机继续进行状态流转,这样逻辑就打通了:
private void MoveNext() { // num = 0 int num = this.<>1__state; try { TaskAwaiter awaiter; if (num != 0) { Console.WriteLine("Let's Go!"); awaiter = Program.TestAsync().GetAwaiter(); if (!awaiter.IsCompleted) { this.<>1__state = num = 0; this.<>u__1 = awaiter; Program.<Main>d__0 stateMachine = this; this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Main>d__0>(ref awaiter, ref stateMachine); return; } } else { awaiter = this.<>u__1; this.<>u__1 = new TaskAwaiter(); // 状态机状态从 0 流转到 -1 this.<>1__state = num = -1; } // 结束对 TestAsync() 的等待 awaiter.GetResult(); // 执行延续方法 Console.Write(" World!"); } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } // 状态机状态从 -1 流转到 -2 this.<>1__state = -2; // 设置异步 Main 方法最终返回结果 this.<>t__builder.SetResult(); }
至此,整个异步方法的执行就结束了,经过一张图总结一下:
最后,咱们看一下各个线程的状态,看看和你的推理是否一致(若是有不清楚的联系我,我会经过文字补充):
理解了async await的简单使用,那你可曾想过,若是有多个 async await 嵌套,那会出现什么状况呢?接下来就改造一下咱们的例子,来研究研究:
static Task TestAsync() { return Task.Run(async () => { // 增长了这行 await Task.Run(() => { Console.Write("Say: "); }); Console.Write("Hello"); }); }
反编译以后的代码,上面已经讲解的我就再也不重复贴了,主要看看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 在同一方法中顺序执行,又会是何种景象呢?一样,先来个例子:
static async Task Main(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 { // Fields public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; public string[] args; private TaskAwaiter <>u__1; // Methods private void MoveNext() { int num = this.<>1__state; try { TaskAwaiter awaiter; TaskAwaiter awaiter2; if (num != 0) { if (num == 1) { awaiter = this.<>u__1; this.<>u__1 = default(TaskAwaiter); this.<>1__state = -1; goto IL_D8; } Console.WriteLine("Let's Go!"); awaiter2 = Program.Test1Async().GetAwaiter(); if (!awaiter2.IsCompleted) { this.<>1__state = 0; this.<>u__1 = awaiter2; Program.<Main>d__0 <Main>d__ = this; this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Main>d__0>(ref awaiter2, ref <Main>d__); return; } } else { awaiter2 = this.<>u__1; this.<>u__1 = default(TaskAwaiter); this.<>1__state = -1; } awaiter2.GetResult(); // 待 Test1Async 完成后,继续执行 Test2Async awaiter = Program.Test2Async().GetAwaiter(); if (!awaiter.IsCompleted) { this.<>1__state = 1; this.<>u__1 = awaiter; Program.<Main>d__0 <Main>d__ = this; this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Main>d__0>(ref awaiter, ref <Main>d__); return; } IL_D8: awaiter.GetResult(); Console.Write(" World!"); } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } this.<>1__state = -2; this.<>t__builder.SetResult(); } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } }
可见,就是一个状态机状态一直流转就完事了。咱们就看看线程状态吧:
上面咱们都是经过控制台举的例子,这是没有任何SynchronizationContext
的,可是WPF(Winform同理)就不一样了,在UI线程中,它拥有属于本身的DispatcherSynchronizationContext
。
private async void Button_Click(object sender, RoutedEventArgs e) { // UI 线程会一直保持 Running 状态,不会致使 UI 假死 Show(Thread.CurrentThread); await TestAsync(); Show(Thread.CurrentThread); } private Task TestAsync() { return Task.Run(() => { Show(Thread.CurrentThread); }); } private static void Show(Thread thread) { MessageBox.Show($"{nameof(thread.ManagedThreadId)}: {thread.ManagedThreadId}" + $"\n{nameof(thread.ThreadState)}: {thread.ThreadState}"); }
经过使用DispatcherSynchronizationContext
执行延续方法,又回到了 UI 线程中