理解C#中的 async await
<h4 id="前言">前言</h4><p>一个老掉牙的话题,园子里的相关优秀文章已经有很多了,我写这篇文章完全是想以自己的思维方式来谈一谈自己的理解。(PS:文中涉及到了大量反编译源码,需要静下心来细细品味)</p>
<h2 id="从简单开始">从简单开始</h2>
<p>为了更容易理解这个问题,我们举一个简单的例子:用异步的方式在控制台上分两步输出“Hello World!”,我这边使用的是Framework 4.5.2</p>
<pre><code>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");
});
}
}
</code></pre>
<h2 id="探究反编译后的源码">探究反编译后的源码</h2>
<p>接下来我们使用 .NET reflector (也可使用 dnSpy 等) 反编译一下程序集,然后一步一步来探究 async await 内部的奥秘。</p>
<h3 id="main方法">Main方法</h3>
<pre><code>
private static void <Main>(string[] args)
{
Main(args).GetAwaiter().GetResult();
}
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 接口
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() { }
private void SetStateMachine(IAsyncStateMachine stateMachine) { }
}
</code></pre>
<p>卧槽!竟然有两个 Main 方法:一个同步、一个异步。原来,虽然我们写代码时为了在 Main 方法中方便异步等待,将 void Main 改写成了async Task Main,但是实际上程序入口仍是我们熟悉的那个 void Main。</p>
<p>另外,我们可以看到异步 Main 方法被标注了<code>AsyncStateMachine</code>特性,这是因为在我们的源代码中,该方法带有修饰符<code>async</code>,表示该方法是一个异步方法。</p>
<p>好,我们先看一下异步Main方法内部实现,它主要做了三件事:</p>
<ol>
<li>首先,创建了一个类型为<code><Main>d__0</code>的状态机 stateMachine,并初始化了公共变量 <strong><>t__builder</strong>、args、<strong><>1__state = -1</strong>
<ul>
<li><>t__builder:负责异步相关的操作,是实现异步 Main 方法异步的核心</li>
<li><>1__state:状态机的当前状态</li>
</ul>
</li>
<li>然后,调用<code>Start</code>方法,借助 stateMachine, 来执行我们在异步 Main 方法中写的代码</li>
<li>最后,将指示异步 Main 方法运行状态的<code>Task</code>对象返回出去</li>
</ol>
<h3 id="start">Start</h3>
<p>首先,我们先来看一下<code>Start</code>的内部实现</p>
<pre><code>// 所属结构体:AsyncTaskMethodBuilder
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();
}
}
</code></pre>
<p>我猜,你只能看懂<code>stateMachine.MoveNext()</code>,对不对?好,那我们就来看看这个状态机类<code><Main>d__0</code>,并且着重看它的方法<code>MoveNext</code>。</p>
<h3 id="movenext">MoveNext</h3>
<pre><code>
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();
}
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/1010000/202101/1010000-20210120165435649-2140245165.png" alt="" loading="lazy"></p>
<p>先简单理一下内部逻辑:</p>
<ol>
<li>设置变量 num = -1,此时 num != 0,则会进入第一个if语句,</li>
<li>首先,执行<code>Console.WriteLine("Let's Go!")</code></li>
<li>然后,调用异步方法<code>TestAsync</code>,<code>TestAsync</code>方法会在另一个线程池线程中执行,并获取指示该方法运行状态的 awaiter</li>
<li>如果此时<code>TestAsync</code>方法已执行完毕,则像没有异步一般:
<ol>
<li>继续执行接下来的<code>Console.Write(" World!")</code></li>
<li>最后设置 <>1__state = -2,并设置异步 Main 方法的返回结果</li>
</ol>
</li>
<li>如果此时<code>TestAsync</code>方法未执行完毕,则:
<ol>
<li>设置 <>1__state = num = 0</li>
<li>调用<code>AwaitUnsafeOnCompleted</code>方法,用于配置当<code>TestAsync</code>方法完成时的延续,即<code>Console.Write(" World!")</code></li>
<li>返回指示异步 Main 方法执行状态的 Task 对象,由于同步 Main 方法中通过使用<code>GetResult()</code>同步阻塞主线程等待任务结束,所以不会释放主线程(废话,如果释放了程序就退出了)。不过对于其他子线程,一般会释放该线程</li>
</ol>
</li>
</ol>
<p>大部分逻辑我们都可以很容易的理解,唯一需要深入研究的就是<code>AwaitUnsafeOnCompleted</code>,那我们接下来就看看它的内部实现</p>
<h3 id="awaitunsafeoncompleted">AwaitUnsafeOnCompleted</h3>
<pre><code>// 所属结构体:AsyncTaskMethodBuilder
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>
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);
}
}
</code></pre>
<p>咱们一步一步来,先看一下<code>GetCompletionAction</code>的实现:</p>
<pre><code>// 所属结构体:AsyncMethodBuilderCore
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;
}
</code></pre>
<p>发现一个熟悉的家伙——<strong>ExecutionContext</strong>,它是用来给咱们延续方法(即<code>Console.Write(" World!");</code>)提供运行环境的,注意这里用的是<code>FastCapture()</code>,该内部方法并未捕获<code>SynchronizationContext</code>,因为不需要流动它。什么?你说你不认识它?大眼瞪小眼?那你应该好好看看《理解C#中的ExecutionContext vs SynchronizationContext》了</p>
<p>接着来到<code>new MoveNextRunner(context, this.m_stateMachine)</code>,这里初始化了 runner,我们看看构造函数中做了什么:</p>
<pre><code>
internal MoveNextRunner(ExecutionContext context, IAsyncStateMachine stateMachine)
{
// 将 ExecutionContext 保存了下来
this.m_context = context;
// 将 stateMachine 保存了下来(不过此时为 null)
this.m_stateMachine = stateMachine;
}
</code></pre>
<p>往下来到<code>defaultContextAction = new Action(runner.Run)</code>,你可以发现,最终咱们返回的就是这个 defaultContextAction ,所以这个<code>runner.Run</code>至关重要,不过别着急,我们等用到它的时候我们再来看其内部实现。</p>
<p>最后,回到<code>AwaitUnsafeOnCompleted</code>方法,继续往下走。构建指示异步 Main 方法执行状态的 Task 对象,设置当前的状态机后,来到<code>awaiter.UnsafeOnCompleted(completionAction);</code>,要记住,入参 completionAction 就是刚才返回的<code>runner.Run</code>:</p>
<pre><code>// 所属结构体:TaskAwaiter
public void UnsafeOnCompleted(Action continuation)
{
OnCompletedInternal(this.m_task, continuation, true, false);
}
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);
}
</code></pre>
<p>直接来到代码最后一行,看到延续方法的配置</p>
<pre><code>// 所属类:Task
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);
}
}
</code></pre>
<p>对于我们的示例来说,既没有自定义 SynchronizationContext,也没有自定义 TaskScheduler,所以会直接来到最后一个<code>else if (...)</code>,重点在于<code>this.AddTaskContinuation(continuationAction, false)</code>,这个方法会将我们的延续方法添加到 Task 中,以便于当 TestAsync 方法执行完毕时,执行 runner.Run</p>
<h3 id="runnerrun">runner.Run</h3>
<p>好,是时候让我们看看 runner.Run 的内部实现了:</p>
<pre><code>
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();
}
private static void InvokeMoveNext(object stateMachine)
{
((IAsyncStateMachine) stateMachine).MoveNext();
}
</code></pre>
<p>来到<code>ExecutionContext.Run(this.m_context, callback, this.m_stateMachine, true);</code>,这里的 callback 是<code>InvokeMoveNext</code>方法。所以,当<code>TestAsync</code>执行完毕后,就会执行延续方法 runner.Run,也就会执行<code>stateMachine.MoveNext()</code>促使状态机继续进行状态流转,这样逻辑就打通了:</p>
<pre><code>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();
}
</code></pre>
<p>至此,整个异步方法的执行就结束了,通过一张图总结一下:<br>
<img src="https://img2020.cnblogs.com/blog/1010000/202101/1010000-20210120165259645-250717138.png" alt="" loading="lazy"></p>
<p>最后,我们看一下各个线程的状态,看看和你的推理是否一致(如果有不清楚的联系我,我会通过文字补充):<br>
<img src="https://img2020.cnblogs.com/blog/1010000/202101/1010000-20210120165320220-2013763193.gif" alt="" loading="lazy"></p>
<h2 id="多个-async-await-嵌套">多个 async await 嵌套</h2>
<p>理解了async await的简单使用,那你可曾想过,如果有多个 async await 嵌套,那会出现什么情况呢?接下来就改造一下我们的例子,来研究研究:</p>
<pre><code>static Task TestAsync()
{
return Task.Run(async () =>
{
// 增加了这行
await Task.Run(() =>
{
Console.Write("Say: ");
});
Console.Write("Hello");
});
}
</code></pre>
<p>反编译之后的代码,上面已经讲解的我就不再重复贴了,主要看看<code>TestAsync()</code>就行了:</p>
<pre><code>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;
});
</code></pre>
<p>哦!原来,async await 的嵌套也就是状态机的嵌套,相信你通过上面的状态机状态流转,也能够梳理除真正的执行逻辑,那我们就只看一下线程状态吧:<br>
<img src="https://img2020.cnblogs.com/blog/1010000/202101/1010000-20210120165459091-2100640352.gif" alt="" loading="lazy"></p>
<p>这也印证了我上面所说的:当子线程完成执行任务时,会被释放,或回到线程池供其他线程使用。</p>
<h2 id="多个-async-await-在同一方法中顺序执行">多个 async await 在同一方法中顺序执行</h2>
<p>又可曾想过,如果有多个 async await 在同一方法中顺序执行,又会是何种景象呢?同样,先来个例子:</p>
<pre><code>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");
});
}
</code></pre>
<p>直接看状态机:</p>
<pre><code>
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();
}
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}
</code></pre>
<p>可见,就是一个状态机状态一直流转就完事了。我们就看看线程状态吧:<br>
<img src="https://img2020.cnblogs.com/blog/1010000/202101/1010000-20210120165515998-311183181.gif" alt="" loading="lazy"></p>
<h2 id="wpf中使用-async-await">WPF中使用 async await</h2>
<p>上面我们都是通过控制台举的例子,这是没有任何<code>SynchronizationContext</code>的,但是WPF(Winform同理)就不同了,在UI线程中,它拥有属于自己的<code>DispatcherSynchronizationContext</code>。</p>
<pre><code>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}");
}
</code></pre>
<p>通过使用<code>DispatcherSynchronizationContext</code>执行延续方法,又回到了 UI 线程中<br>
<img src="https://img2020.cnblogs.com/blog/1010000/202101/1010000-20210120165534935-171511763.gif" alt="" loading="lazy"></p><br><br>
来源:https://www.cnblogs.com/xiaoxiaotank/p/14303803.html
頁:
[1]