c#異步操作async?await狀態(tài)機(jī)的總結(jié)(推薦)
前言
前一段時(shí)間得閑的時(shí)候優(yōu)化了一下我之前的輪子[DotNetCoreRpc]小框架,其中主要的優(yōu)化點(diǎn)主要是關(guān)于RPC異步契約調(diào)用的相關(guān)邏輯。在此過(guò)程中進(jìn)一步了解了關(guān)于async和await異步操作相關(guān)的知識(shí)點(diǎn),加深了異步操作的理解,因此總結(jié)一下。關(guān)于async和await每個(gè)人都有自己的理解,甚至關(guān)于異步和同步亦或者關(guān)于異步和多線程每個(gè)人也都有自己的理解。因此,如果本文涉及到個(gè)人觀點(diǎn)與您的觀點(diǎn)不一致的時(shí)候請(qǐng)勿噴。結(jié)論固然重要,但是在這個(gè)過(guò)程中的引發(fā)的思考也很重要。
async await是語(yǔ)法糖
大家應(yīng)該都比較清楚async和await這對(duì)關(guān)鍵字是一組語(yǔ)法糖,關(guān)于語(yǔ)法糖大家可以理解為,編碼過(guò)程中寫(xiě)了一個(gè)關(guān)鍵字,但是編譯的時(shí)候會(huì)把它編譯成別的東西,主要是用來(lái)提升開(kāi)發(fā)效率。比如我有一段關(guān)于async和await相關(guān)的代碼,如下所示
var taskOne = await TaskOne();
Console.WriteLine(taskOne);
Console.ReadLine();
static async Task<string> TaskOne()
{
var httpResponse = await ClassFactory.Client.GetAsync("https://www.cnblogs.com");
var content = await httpResponse.Content.ReadAsStringAsync();
return content;
}
public class ClassFactory
{
public static HttpClient Client = new HttpClient();
}這段代碼是基于c#頂級(jí)語(yǔ)句聲明的,它是缺省Main方法的,不過(guò)在編譯的時(shí)候編譯器會(huì)幫我們補(bǔ)齊Main方法,因?yàn)閳?zhí)行的時(shí)候JIT需要Main方法作為執(zhí)行入口。關(guān)于如何查看編譯后的代碼。我經(jīng)常使用的是兩個(gè)工具,分別是ILSpy和dnSpy。這倆工具的區(qū)別在于ILSpy生成的代碼更清晰,dnSpy生成的源碼是可以直接調(diào)試的。需要注意的是如果使用的是ILSpy如果查看語(yǔ)法糖本質(zhì)的話,需要在ILSpy上選擇比語(yǔ)法糖版本低的版本,比如c# async和await關(guān)鍵字是在c# 5.0版本中引入的,所以我們這里我們?cè)?code>ILSpy里需要選擇c#4.0或以下版本,入下圖所示

如果使用的是dnSpy的話,需要在調(diào)試-->選項(xiàng)-->反編譯器中設(shè)置相關(guān)選項(xiàng),如下所示

這樣就可以看到編譯后生成的代碼了。
生成的狀態(tài)機(jī)
圍繞上面的示例我這里使用的Debug模式下編譯生成的dll使用的ILSpy進(jìn)行反編譯,因?yàn)檫@里我需要讓編譯的源碼看起來(lái)更清晰一點(diǎn),而不是調(diào)試。如下所示首先看Main方法
//因?yàn)槲覀兩厦娲avar taskOne = await TaskOne()
//使用了await語(yǔ)法糖,所以被替換成了狀態(tài)機(jī)調(diào)用
[AsyncStateMachine(typeof(<<Main>$>d__0))]
[DebuggerStepThrough]
private static Task <Main>$(string[] args)
{
//創(chuàng)建狀態(tài)機(jī)實(shí)例
<<Main>$>d__0 stateMachine = new <<Main>$>d__0();
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.args = args;
//設(shè)置狀態(tài)-1
stateMachine.<>1__state = -1;
//啟動(dòng)狀態(tài)機(jī)
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
//這是系統(tǒng)默認(rèn)幫我們生成的static void Main的入口方法
[SpecialName]
[DebuggerStepThrough]
private static void <Main>(string[] args)
{
//同步調(diào)用<Main>$方法
<Main>$(args).GetAwaiter().GetResult();
}上面的代碼就是編譯器為我們生成的Main方法,通過(guò)這里我們可以得到兩條信息
頂級(jí)語(yǔ)句編譯器會(huì)幫我們生成固定的入口函數(shù)格式,即static void Main這種標(biāo)準(zhǔn)格式- 編譯器遇到
await關(guān)鍵字則會(huì)編譯出一段狀態(tài)機(jī)相關(guān)的代碼,把我們的邏輯放到編譯的狀態(tài)機(jī)類里
通過(guò)上面我們可以看到<<Main>$>d__0 這個(gè)類是編譯器幫我們生成的,我們可以看一下生成的代碼
[CompilerGenerated]
private sealed class <<Main>$>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
public string[] args;
private string <taskOne>5__1;
private string <>s__2;
[System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })]
private TaskAwaiter<string> <>u__1;
private void MoveNext()
{
int num = <>1__state;
try
{
TaskAwaiter<string> awaiter;
//num的值來(lái)自<>1__state,由于在創(chuàng)建狀態(tài)機(jī)的時(shí)候傳遞的是-1所以一定會(huì)走到這個(gè)邏輯
if (num != 0)
{
//調(diào)用TaskOne方法,也就是上面我們寫(xiě)的業(yè)務(wù)方法
//這個(gè)方法返回的是TaskAwaiter<>實(shí)例,以為我們TaskOne方法是異步方法
awaiter = <<Main>$>g__TaskOne|0_0().GetAwaiter();
//判斷任務(wù)是否執(zhí)行完成
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<<Main>$>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter<string>);
num = (<>1__state = -1);
}
//調(diào)用GetResult()方法獲取異步執(zhí)行結(jié)果
<>s__2 = awaiter.GetResult();
<taskOne>5__1 = <>s__2;
<>s__2 = null;
//這里對(duì)應(yīng)我們上面的輸出調(diào)用TaskOne方法的結(jié)果
Console.WriteLine(<taskOne>5__1);
Console.ReadLine();
}
catch (Exception exception)
{
<>1__state = -2;
<taskOne>5__1 = null;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<taskOne>5__1 = null;
<>t__builder.SetResult();
}
void IAsyncStateMachine.MoveNext()
{
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine([System.Runtime.CompilerServices.Nullable(1)] IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine([System.Runtime.CompilerServices.Nullable(1)] IAsyncStateMachine stateMachine)
{
this.SetStateMachine(stateMachine);
}
}
這里的代碼可以看到編譯器生成的代碼,其實(shí)這就是對(duì)應(yīng)上面我們寫(xiě)的代碼
var taskOne = await TaskOne(); Console.WriteLine(taskOne); Console.ReadLine();
因?yàn)槲覀兪褂昧?code>await關(guān)鍵字,所以它幫我們生成了IAsyncStateMachine類,里面的核心邏輯咱們待會(huì)在介紹,因?yàn)榻裉斓闹黝}TaskOne方法還沒(méi)介紹完成呢,TaskOne生成的代碼如下所示
//TaskOne方法編譯時(shí)生成的代碼
[CompilerGenerated]
private sealed class <<<Main>$>g__TaskOne|0_0>d : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<string> <>t__builder;
private HttpResponseMessage <httpResponse>5__1;
private string <content>5__2;
private HttpResponseMessage <>s__3;
private string <>s__4;
[System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })]
private TaskAwaiter<HttpResponseMessage> <>u__1;
[System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })]
private TaskAwaiter<string> <>u__2;
private void MoveNext()
{
int num = <>1__state;
string result;
try
{
//因?yàn)槲覀兪褂昧藘纱蝍wait所以這里會(huì)有兩個(gè)TaskAwaiter<>實(shí)例
//var httpResponse = await ClassFactory.Client.GetAsync("https://www.cnblogs.com");
//var content = await httpResponse.Content.ReadAsStringAsync();
TaskAwaiter<string> awaiter;
TaskAwaiter<HttpResponseMessage> awaiter2;
if (num != 0)
{
if (num == 1)
{
awaiter = <>u__2;
<>u__2 = default(TaskAwaiter<string>);
num = (<>1__state = -1);
goto IL_0100;
}
//這段邏輯針對(duì)的是我們手寫(xiě)的這段代碼
//await ClassFactory.Client.GetAsync("https://www.cnblogs.com")
awaiter2 = ClassFactory.Client.GetAsync("https://www.cnblogs.com").GetAwaiter();
//判斷任務(wù)是否完成
if (!awaiter2.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter2;
<<<Main>$>g__TaskOne|0_0>d stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
return;
}
}
else
{
awaiter2 = <>u__1;
<>u__1 = default(TaskAwaiter<HttpResponseMessage>);
num = (<>1__state = -1);
}
//同步獲取HttpResponseMessage結(jié)果實(shí)例
<>s__3 = awaiter2.GetResult();
<httpResponse>5__1 = <>s__3;
<>s__3 = null;
//這段代碼對(duì)應(yīng)生成的則是await httpResponse.Content.ReadAsStringAsync()
awaiter = <httpResponse>5__1.Content.ReadAsStringAsync().GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 1);
<>u__2 = awaiter;
<<<Main>$>g__TaskOne|0_0>d stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
goto IL_0100;
IL_0100:
//同步獲取httpResponse.Content.ReadAsStringAsync()放的結(jié)果
<>s__4 = awaiter.GetResult();
<content>5__2 = <>s__4;
<>s__4 = null;
result = <content>5__2;
}
catch (Exception exception)
{
<>1__state = -2;
<httpResponse>5__1 = null;
<content>5__2 = null;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<httpResponse>5__1 = null;
<content>5__2 = null;
//調(diào)用AsyncTaskMethodBuilder<>方法放置httpResponse.Content.ReadAsStringAsync()結(jié)果
<>t__builder.SetResult(result);
}
void IAsyncStateMachine.MoveNext()
{
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine([System.Runtime.CompilerServices.Nullable(1)] IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine([System.Runtime.CompilerServices.Nullable(1)] IAsyncStateMachine stateMachine)
{
this.SetStateMachine(stateMachine);
}
}
到這里為止,這些方法就是編譯器幫我們生成的代碼,也就是這些代碼就在生成好的dll里的。
啟動(dòng)狀態(tài)機(jī)
接下來(lái)我們分析一下?tīng)顟B(tài)機(jī)的調(diào)用過(guò)程,回到上面的stateMachine.<>t__builder.Start(ref stateMachine)這段狀態(tài)機(jī)啟動(dòng)代碼,我們跟進(jìn)去看一下里面的邏輯
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[DebuggerStepThrough]
public void Start<[Nullable(0)] TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
//調(diào)用了AsyncMethodBuilderCore的Start方法并傳遞狀態(tài)機(jī)實(shí)例
//即<<Main>$>d__0 stateMachine = new <<Main>$>d__0()實(shí)例
AsyncMethodBuilderCore.Start(ref stateMachine);
}
//AsyncMethodBuilderCore的Start方法
[DebuggerStepThrough]
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
if (stateMachine == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
}
//獲取當(dāng)前線程實(shí)例
Thread currentThread = Thread.CurrentThread;
//獲取當(dāng)前執(zhí)行上下文
ExecutionContext executionContext = currentThread._executionContext;
//獲取當(dāng)前同步上下文
SynchronizationContext synchronizationContext = currentThread._synchronizationContext;
try
{
//調(diào)用狀態(tài)機(jī)的MoveNext方法
stateMachine.MoveNext();
}
finally
{
//執(zhí)行完MoveNext之后
//還原SynchronizationContext同步上下文到當(dāng)前實(shí)例
if (synchronizationContext != currentThread._synchronizationContext)
{
currentThread._synchronizationContext = synchronizationContext;
}
//還原ExecutionContext執(zhí)行上下文到當(dāng)前實(shí)例
ExecutionContext executionContext2 = currentThread._executionContext;
if (executionContext != executionContext2)
{
//執(zhí)行完成之后把執(zhí)行上下文裝載到當(dāng)前線程
ExecutionContext.RestoreChangedContextToThread(currentThread, executionContext, executionContext2);
}
}
}
執(zhí)行完異步任務(wù)之后,會(huì)判斷SynchronizationContext同步上下文環(huán)境和ExecutionContext執(zhí)行上下文環(huán)境,保證異步異步之后的可以操作UI線程上的控件,或者異步的后續(xù)操作和之前的操作處在相同的執(zhí)行上線文中。
題外話:ExecutionContext 是一個(gè)用于傳遞狀態(tài)和環(huán)境信息的類,它可以在不同的執(zhí)行上下文之間傳遞狀態(tài)。執(zhí)行上下文表示代碼執(zhí)行的環(huán)境,包括線程、應(yīng)用程序域、安全上下文和調(diào)用上下文等。ExecutionContext 對(duì)象包含當(dāng)前線程上下文的所有信息,如當(dāng)前線程的安全上下文、邏輯執(zhí)行上下文、同步上下文和物理執(zhí)行上下文等。它提供了方法,可以將當(dāng)前的執(zhí)行上下文復(fù)制到另一個(gè)線程中,或者在異步操作之間保存和還原執(zhí)行上下文。在異步編程中,使用 ExecutionContext 可以確保代碼在正確的上下文中運(yùn)行,并且傳遞必要的狀態(tài)和環(huán)境信息。
SynchronizationContext 是一個(gè)用于同步執(zhí)行上下文和處理 UI 線程消息循環(huán)的抽象類。它可以將回調(diào)方法派發(fā)到正確的線程中執(zhí)行,避免了跨線程訪問(wèn)的問(wèn)題,并提高了應(yīng)用程序的響應(yīng)性和可靠性。在異步編程中,可以使用 SynchronizationContext.Current 屬性獲取當(dāng)前線程的同步上下文,并使用同步上下文的 Post 或 Send 方法將回調(diào)方法派發(fā)到正確的線程中執(zhí)行。
由于調(diào)用stateMachine.<>t__builder.Start(ref stateMachine)傳遞的是new <<Main>$>d__0()實(shí)例,所以這里核心就是在調(diào)用生成的狀態(tài)機(jī)IAsyncStateMachine實(shí)例,即我們上面的<<Main>$>d__0類的MoveNext()方法
void IAsyncStateMachine.MoveNext()
{
this.MoveNext();
}由上面的代碼可知,本質(zhì)是調(diào)用的私有的MoveNext()方法,即會(huì)執(zhí)行我們真實(shí)邏輯的那個(gè)方法。由于編譯器生成的狀態(tài)機(jī)代碼的邏輯是大致相同的,所以我們直接來(lái)看,我們業(yè)務(wù)具體落實(shí)的代碼即<<<Main>$>g__TaskOne|0_0>d狀態(tài)機(jī)類里的,私有的那個(gè)MoveNext方法代碼
AsyncTaskMethodBuilder<string> <>t__builder;
TaskAwaiter<HttpResponseMessage> awaiter2;
if (num != 0)
{
if (num == 1)
{}
//ClassFactory.Client.GetAsyn()方法生成的邏輯
awaiter2 = ClassFactory.Client.GetAsync("https://www.cnblogs.com").GetAwaiter();
if (!awaiter2.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter2;
<<<Main>$>g__TaskOne|0_0>d stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
return;
}
//同步獲取異步結(jié)果
<>s__4 = awaiter.GetResult();
}
else
{}
TaskAwaiter<string> awaiter;
//httpResponse.Content.ReadAsStringAsync()方法生成的邏輯
awaiter = <httpResponse>5__1.Content.ReadAsStringAsync().GetAwaiter();
//判斷任務(wù)是否完成
if (!awaiter.IsCompleted)
{
num = (<>1__state = 1);
<>u__2 = awaiter;
<<<Main>$>g__TaskOne|0_0>d stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
//同步獲取異步結(jié)果,并將返回值裝載
result= awaiter.GetResult();
<>t__builder.SetResult(result);
當(dāng)然這里我們省了里面的很多邏輯,為了讓結(jié)構(gòu)看起來(lái)更清晰一點(diǎn)。
通過(guò)上面的它生成的結(jié)構(gòu)來(lái)看,我們寫(xiě)代碼的時(shí)候一個(gè)方法里的每個(gè)await都會(huì)被生成一個(gè)TaskAwaiter邏輯,根據(jù)當(dāng)前異步狀態(tài)IsCompleted判斷任務(wù)是否完成,來(lái)執(zhí)行下一步操作。如果任務(wù)未完成IsCompleted為false則調(diào)用AsyncTaskMethodBuilder實(shí)例的AwaitUnsafeOnCompleted方法,如果異步已完成則直接獲取異步結(jié)果,進(jìn)行下一步。
執(zhí)行異步任務(wù)
通過(guò)上面的邏輯我們可以看到,如果異步任務(wù)沒(méi)有完成則調(diào)用了AsyncTaskMethodBuilder實(shí)例的AwaitUnsafeOnCompleted方法。接下來(lái)我們就看下AwaitUnsafeOnCompleted方法的實(shí)現(xiàn)
public void AwaitUnsafeOnCompleted<[Nullable(0)] TAwaiter, [Nullable(0)] TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
{
//調(diào)用AwaitUnsafeOnCompleted方法
AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine, ref m_task);
}
internal static void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine, [NotNull] ref Task<TResult> taskField) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
{
//創(chuàng)建IAsyncStateMachineBox實(shí)例
IAsyncStateMachineBox stateMachineBox = GetStateMachineBox(ref stateMachine, ref taskField);
//調(diào)用AwaitUnsafeOnCompleted()方法
AwaitUnsafeOnCompleted(ref awaiter, stateMachineBox);
}
internal static void AwaitUnsafeOnCompleted<TAwaiter>(ref TAwaiter awaiter, IAsyncStateMachineBox box) where TAwaiter : ICriticalNotifyCompletion
{
//判斷awaiter實(shí)例類型
if (default(TAwaiter) != null && awaiter is ITaskAwaiter)
{
//獲取TaskAwaiter實(shí)例的m_task屬性即Task類型
TaskAwaiter.UnsafeOnCompletedInternal(Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter).m_task, box, true);
return;
}
if (default(TAwaiter) != null && awaiter is IConfiguredTaskAwaiter)
{
//與上面邏輯一致m_task屬性即Task類型本質(zhì)他們都在操作Task
ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter reference = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
TaskAwaiter.UnsafeOnCompletedInternal(reference.m_task, box, reference.m_continueOnCapturedContext);
return;
}
if (default(TAwaiter) != null && awaiter is IStateMachineBoxAwareAwaiter)
{
try
{
//調(diào)用IStateMachineBoxAwareAwaiter實(shí)例的AwaitUnsafeOnCompleted方法
((IStateMachineBoxAwareAwaiter)(object)awaiter).AwaitUnsafeOnCompleted(box);
return;
}
catch (Exception exception)
{
System.Threading.Tasks.Task.ThrowAsync(exception, null);
return;
}
}
try
{
//調(diào)用ICriticalNotifyCompletion實(shí)例的UnsafeOnCompleted方法
awaiter.UnsafeOnCompleted(box.MoveNextAction);
}
catch (Exception exception2)
{
System.Threading.Tasks.Task.ThrowAsync(exception2, null);
}
}
通過(guò)這個(gè)方法我們可以看到傳遞進(jìn)來(lái)的TAwaiter都是ICriticalNotifyCompletion的實(shí)現(xiàn)類,所以他們的行為存在一致性,只是具體的實(shí)現(xiàn)動(dòng)作根據(jù)不同的實(shí)現(xiàn)類型來(lái)判斷。
- 如果是
ITaskAwaiter類的話直接調(diào)用TaskAwaiter.UnsafeOnCompletedInternal()方法,傳遞了TaskAwaiter.m_task屬性,這是一個(gè)Task類型的屬性 - 如果是
IConfiguredTaskAwaiter類型的話,也是調(diào)用了TaskAwaiter.UnsafeOnCompletedInternal()方法,傳遞了ConfiguredTaskAwaiter.m_task屬性,這也是一個(gè)Task類型的屬性 - 如果是
IStateMachineBoxAwareAwaiter類型的話,調(diào)用IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted()方法,傳遞的是當(dāng)前的IAsyncStateMachineBox狀態(tài)機(jī)盒子實(shí)例,具體實(shí)現(xiàn)咱們待會(huì)看 - 如果上面的條件都不滿足的話,則調(diào)用
ICriticalNotifyCompletion.UnsafeOnCompleted()方法,傳遞的是IAsyncStateMachineBox.MoveNextAction方法,IAsyncStateMachineBox實(shí)現(xiàn)類包裝了IAsyncStateMachine實(shí)現(xiàn)類,這里的stateMachineBox.MoveNextAction本質(zhì)是在執(zhí)行IAsyncStateMachine的MoveNext的方法,即我們狀態(tài)機(jī)里我們自己寫(xiě)的業(yè)務(wù)邏輯。
我們首先來(lái)看一下StateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted()方法,找到一個(gè)實(shí)現(xiàn)類。因?yàn)樗膶?shí)現(xiàn)類有好幾個(gè),比如ConfiguredValueTaskAwaiter、ValueTaskAwaiter、YieldAwaitable等,這里咱們選擇有類型的ConfiguredValueTaskAwaiter實(shí)現(xiàn)類,看一下AwaitUnsafeOnCompleted方法
void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box)
{
object? obj = _value._obj;
Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource);
if (obj is Task t)
{
//如果是Task類型的話會(huì)調(diào)用TaskAwaiter.UnsafeOnCompletedInternal方法,也是上面咱們多次提到的
TaskAwaiter.UnsafeOnCompletedInternal(t, box, _value._continueOnCapturedContext);
}
else if (obj != null)
{
Unsafe.As<IValueTaskSource>(obj).OnCompleted(ThreadPool.s_invokeAsyncStateMachineBox, box, _value._token,
_value._continueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None);
}
else
{
//兜底的方法也是TaskAwaiter.UnsafeOnCompletedInternal
TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, _value._continueOnCapturedContext);
}
}
可以看到ConfiguredValueTaskAwaiter.AwaitUnsafeOnCompleted()方法最終也是執(zhí)行到了TaskAwaiter.UnsafeOnCompletedInternal()方法,這個(gè)咱們上面已經(jīng)多次提到了。接下里咱們?cè)賮?lái)看一下ICriticalNotifyCompletion.UnsafeOnCompleted()方法里的實(shí)現(xiàn)是啥,咱們找到它的一個(gè)常用的實(shí)現(xiàn)類,也是咱們上面狀態(tài)機(jī)幫咱們生成的TaskAwaiter<>類里的實(shí)現(xiàn)
public void UnsafeOnCompleted(Action continuation)
{
TaskAwaiter.OnCompletedInternal(m_task, continuation, true, false);
}
//TaskAwaiter的OnCompletedInternal方法
internal static void OnCompletedInternal(Task task, Action continuation, bool continueOnCapturedContext, bool flowExecutionContext)
{
ArgumentNullException.ThrowIfNull(continuation, "continuation");
if (TplEventSource.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
{
continuation = OutputWaitEtwEvents(task, continuation);
}
//這里調(diào)用了Task的SetContinuationForAwait方法
task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext);
}咱們看到了這里調(diào)用的是Task的SetContinuationForAwait方法,上面我們提到的AwaitUnsafeOnCompleted方法里直接調(diào)用了TaskAwaiter的UnsafeOnCompletedInternal方法,咱們可以來(lái)看一下里面的實(shí)現(xiàn)
internal static void UnsafeOnCompletedInternal(Task task, IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
{
if (TplEventSource.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
{
//默認(rèn)情況下我們是沒(méi)有去監(jiān)聽(tīng)EventSource發(fā)布的時(shí)間消息
//如果你開(kāi)啟了EventSource日志的監(jiān)聽(tīng)則會(huì)走到這里
task.SetContinuationForAwait(OutputWaitEtwEvents(task, stateMachineBox.MoveNextAction), continueOnCapturedContext, false);
}
else
{
task.UnsafeSetContinuationForAwait(stateMachineBox, continueOnCapturedContext);
}
}因?yàn)槟J(rèn)是沒(méi)有開(kāi)啟EventSource的監(jiān)聽(tīng),所以上面的兩個(gè)TplEventSource.Log.IsEnabled相關(guān)的邏輯執(zhí)行不到,如果代碼里堅(jiān)挺了相關(guān)的EventSource則會(huì)執(zhí)行這段邏輯。SetContinuationForAwait方法和UnsafeSetContinuationForAwait方法邏輯是一致的,只是因?yàn)槿绻_(kāi)啟了EventSource的監(jiān)聽(tīng)會(huì)發(fā)布事件消息,其中包裝了關(guān)于異步信息的事件相關(guān)。所以我們可以直接來(lái)看UnsafeSetContinuationForAwait方法實(shí)現(xiàn)
internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
{
if (continueOnCapturedContext)
{
//winform wpf等ui線程包含同步上下文SynchronizationContext相關(guān)的信息
//如果存在則直接在SynchronizationContext同步上線文中的Post方法把異步結(jié)果在ui線程中完成回調(diào)執(zhí)行
SynchronizationContext current = SynchronizationContext.Current;
if (current != null && current.GetType() != typeof(SynchronizationContext))
{
SynchronizationContextAwaitTaskContinuation synchronizationContextAwaitTaskContinuation = new SynchronizationContextAwaitTaskContinuation(current, stateMachineBox.MoveNextAction, false);
if (!AddTaskContinuation(synchronizationContextAwaitTaskContinuation, false))
{
synchronizationContextAwaitTaskContinuation.Run(this, false);
}
return;
}
//判斷是否包含內(nèi)部任務(wù)調(diào)度器,如果不是默認(rèn)的TaskScheduler.Default調(diào)度策略,也就是ThreadPoolTaskScheduler的方式執(zhí)行MoveNext
//則使用TaskSchedulerAwaitTaskContinuation的Run方法執(zhí)行MoveNext
TaskScheduler internalCurrent = TaskScheduler.InternalCurrent;
if (internalCurrent != null && internalCurrent != TaskScheduler.Default)
{
TaskSchedulerAwaitTaskContinuation taskSchedulerAwaitTaskContinuation = new TaskSchedulerAwaitTaskContinuation(internalCurrent, stateMachineBox.MoveNextAction, false);
if (!AddTaskContinuation(taskSchedulerAwaitTaskContinuation, false))
{
taskSchedulerAwaitTaskContinuation.Run(this, false);
}
return;
}
}
//執(zhí)行兜底邏輯使用線程池執(zhí)行
if (!AddTaskContinuation(stateMachineBox, false))
{
ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, true);
}
}
上面我們提到過(guò)IAsyncStateMachineBox實(shí)現(xiàn)類包裝了IAsyncStateMachine實(shí)現(xiàn)類,它的stateMachineBox.MoveNextAction本質(zhì)是在執(zhí)行AsyncStateMachine的MoveNext的方法,即我們狀態(tài)機(jī)里的自己的業(yè)務(wù)邏輯。根據(jù)上面的邏輯我們來(lái)大致總結(jié)一下相關(guān)的執(zhí)行策略
- 如果包含
SynchronizationContext同步上下文,也就是winform wpf等ui線程,則直接在SynchronizationContext同步上線文中的Post方法把異步結(jié)果在ui線程中完成回調(diào)執(zhí)行,里面的核心方法咱們待會(huì)會(huì)看到 - 如果
TaskScheduler調(diào)度器不是默認(rèn)的ThreadPoolTaskScheduler調(diào)度器,則使用自定義的TaskScheduler來(lái)執(zhí)行MoveNext方法,統(tǒng)一里面的核心方法咱們待來(lái)看 - 兜底的邏輯則是使用線程池來(lái)執(zhí)行,即使用
ThreadPool的UnsafeQueueUserWorkItemInternal方法
好了上面留下了兩個(gè)核心的方法,沒(méi)有展示相關(guān)的實(shí)現(xiàn),首先咱們來(lái)看下TaskSchedulerAwaitTaskContinuation的Run方法,這個(gè)方法適用于存在同步上下文的場(chǎng)景,來(lái)看下它的核心邏輯
internal sealed override void Run(Task task, bool canInlineContinuationTask)
{
//判斷當(dāng)前線程同步上下文是否和傳遞的同步上下文一致,則直接執(zhí)行,說(shuō)明當(dāng)前線程可以直接使用異步結(jié)果
if (canInlineContinuationTask && m_syncContext == SynchronizationContext.Current)
{
RunCallback(AwaitTaskContinuation.GetInvokeActionCallback(), m_action, ref Task.t_currentTask);
return;
}
//如果不是同一個(gè)同步上下文則執(zhí)行PostAction委托
RunCallback(PostAction, this, ref Task.t_currentTask);
}
private static void PostAction(object state)
{
//通過(guò)傳遞的state來(lái)捕獲執(zhí)行回調(diào)的同步上下文,這里使用的SynchronizationContext的非阻塞的Post方法來(lái)執(zhí)行后續(xù)邏輯
SynchronizationContextAwaitTaskContinuation synchronizationContextAwaitTaskContinuation = (SynchronizationContextAwaitTaskContinuation)state;
synchronizationContextAwaitTaskContinuation.m_syncContext.Post(s_postCallback, synchronizationContextAwaitTaskContinuation.m_action);
}
protected void RunCallback(ContextCallback callback, object state, ref Task currentTask)
{
//捕獲執(zhí)行上下文,異步執(zhí)行完成之后在執(zhí)行上下文中執(zhí)行后續(xù)邏輯
ExecutionContext capturedContext = m_capturedContext;
if (capturedContext == null)
{
//核心邏輯就是再行上面的委托即AwaitTaskContinuation.GetInvokeActionCallback方法或PostAction方法
callback(state);
}
else
{
ExecutionContext.RunInternal(capturedContext, callback, state);
}
}
上面的方法省略了一些邏輯,為了讓邏輯看起來(lái)更清晰,我們可以看到里面的邏輯,即在同步上下文SynchronizationContext中執(zhí)行異步的回調(diào)的結(jié)果。如果當(dāng)前線程就包含同步上下文則直接執(zhí)行,如果不是則使用之前傳遞進(jìn)來(lái)的同步上下文來(lái)執(zhí)行。執(zhí)行的時(shí)候會(huì)嘗試捕獲執(zhí)行上下文。咱們還說(shuō)到了如果TaskScheduler調(diào)度器不是默認(rèn)的ThreadPoolTaskScheduler調(diào)度器,則使用自定義的TaskScheduler來(lái)執(zhí)行MoveNext方法,來(lái)看下里面的核心實(shí)現(xiàn)
internal sealed override void Run(Task ignored, bool canInlineContinuationTask)
{
//如果當(dāng)前的scheduler策略是TaskScheduler.Default即默認(rèn)的ThreadPoolTaskScheduler
//則直接使用默認(rèn)策略調(diào)度任務(wù)
if (m_scheduler == TaskScheduler.Default)
{
base.Run(ignored, canInlineContinuationTask);
return;
}
//如果不是默認(rèn)策略則使用,我們定義的TaskScheduler
Task task = CreateTask(delegate(object state)
{
try
{
((Action)state)();
}
catch (Exception exception)
{
Task.ThrowAsync(exception, null);
}
}, m_action, m_scheduler);//這里的m_scheduler指的是自定義的TaskScheduler
bool flag = canInlineContinuationTask && (TaskScheduler.InternalCurrent == m_scheduler || Thread.CurrentThread.IsThreadPoolThread);
//或者是task其他形式的策略執(zhí)行
if (flag)
{
TaskContinuation.InlineIfPossibleOrElseQueue(task, false);
return;
}
try
{
task.ScheduleAndStart(false);
}
catch (TaskSchedulerException)
{
}
}
這個(gè)邏輯看起來(lái)比較清晰,即根據(jù)Task的執(zhí)行策略TaskScheduler判斷如何執(zhí)行任務(wù),比如默認(rèn)的ThreadPoolTaskScheduler策略,或其他策略,比如單線程策略或者自定義的等等。
上面的執(zhí)行過(guò)程可以總結(jié)為以下兩點(diǎn)
- 是否是
Task調(diào)度,否則執(zhí)行默認(rèn)的ThreadPool.UnsafeQueueUserWorkItemInternal()執(zhí)行。如果是TaskScheduler則判斷是哪一種策略,比如是默認(rèn)的ThreadPoolTaskScheduler或是其它策略亦或是自定義策略等。 - 是否包含同步上下文
SynchronizationContext,比如UI線程,大家都知道修改界面控件需要在UI線程上才能執(zhí)行,但是await操作可能存在線程切換如果await的結(jié)果需要在UI展示需要同步上下文保證異步的結(jié)果在UI線程中執(zhí)行。
線程池和Task關(guān)聯(lián)
如果任務(wù)需要執(zhí)行中,我們總得想辦法把結(jié)果給相應(yīng)的Task實(shí)例,這樣我們才能在執(zhí)行完成之后把得到對(duì)應(yīng)的執(zhí)行狀態(tài)或者執(zhí)行結(jié)果在相關(guān)的Task中體現(xiàn)出來(lái),方便我們判斷Task是否執(zhí)行完成或者獲取相關(guān)的執(zhí)行結(jié)果,在ThreadPoolWorkQueue中有相關(guān)的邏輯具體在DispatchWorkItem方法中
private static void DispatchWorkItem(object workItem, Thread currentThread)
{
//判斷在線程池中自行的任務(wù)書(shū)否是Task任務(wù)
Task task = workItem as Task;
if (task != null)
{
task.ExecuteFromThreadPool(currentThread);
}
else
{
Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
}
}ThreadPool里的線程執(zhí)行了Task的ExecuteWithThreadLocal的方法,核心執(zhí)行方法在Task的ExecuteWithThreadLocal,這樣的話執(zhí)行相關(guān)的結(jié)果就可以體現(xiàn)在Task實(shí)例中,比如Task的IsCompleted屬性判斷是否執(zhí)行完成,或者Task<TResult>的GetResultf方法獲取結(jié)果等等。
Task的FromResult
這里需要注意的是Task.FromResult<TResult>(TResult)這個(gè)方法,相信大家經(jīng)常用到,如果你的執(zhí)行結(jié)果需要包裝成Task<TResult>總會(huì)用到這個(gè)方法。它的意思是創(chuàng)建一個(gè)Task<TResult>,并以指定結(jié)果成功完成。,也就是Task<TResult>的IsCompleted屬性為true,這個(gè)結(jié)論可以在dotnet api中Task.FromResult(TResult)文檔中看到,因?yàn)槲覀冎恍枰盐覀円延械慕Y(jié)果包裝成Task所以不涉及到復(fù)雜的執(zhí)行,這也意味著在生成狀態(tài)機(jī)的時(shí)候MoveNext方法里的邏輯判斷IsCompleted時(shí)候代表任務(wù)是直接完成的,會(huì)直接通過(guò)GetResult()獲取到結(jié)果,不需要AwaitUnsafeOnCompleted去根據(jù)執(zhí)行策略執(zhí)行
private void MoveNext()
{
int num = <>1__state;
try
{
TaskAwaiter<string> awaiter;
if (num != 0)
{
awaiter = Task.FromResult("Hello World").GetAwaiter();
//這里的IsCompleted會(huì)為true不會(huì)執(zhí)行相關(guān)的執(zhí)行策略
if (!awaiter.IsCompleted)
{
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
}
<>s__2 = awaiter.GetResult();
}
catch (Exception exception)
{
}
<>t__builder.SetResult();
}總結(jié)
本文主要是展示了近期對(duì)async和await生成的狀態(tài)機(jī)的研究,大概了解了相關(guān)的執(zhí)行過(guò)程。由于異步編程涉及到的東西比較多,而且相當(dāng)復(fù)雜,足夠?qū)懸槐緯?shū)。所以本文設(shè)計(jì)到的不過(guò)是一些皮毛,也由于本人能力有限理解的不一定對(duì),還望諒解。通過(guò)本文大家知道async和await是語(yǔ)法糖,會(huì)生成狀態(tài)機(jī)相關(guān)代碼,讓我們來(lái)總結(jié)一下
- 首先
async和await是語(yǔ)法糖,會(huì)生成狀態(tài)機(jī)類并填充我們編寫(xiě)的業(yè)務(wù)代碼相關(guān) - 如果是未完成任務(wù)也就是
IsCompleted為false則會(huì)執(zhí)行相關(guān)的邏輯去執(zhí)行任務(wù)- 是否是
Task調(diào)度,否則執(zhí)行默認(rèn)的ThreadPool.UnsafeQueueUserWorkItemInternal()執(zhí)行。如果是TaskScheduler則判斷是哪一種策略,比如是默認(rèn)的ThreadPoolTaskScheduler或是其它策略亦或是自定義策略等。 - 是否包含同步上下文
SynchronizationContext,比如UI線程,大家都知道修改界面控件需要在UI線程上才能執(zhí)行,但是await操作可能存在線程切換如果await的結(jié)果需要在UI展示需要同步上下文保證異步的結(jié)果在UI線程中執(zhí)行。
- 是否是
- 需要注意的是
Task.FromResult<TResult>(TResult)這個(gè)方法,它的意思是創(chuàng)建一個(gè)Task<TResult>,并以指定結(jié)果成功完成。,也就是Task<TResult>的IsCompleted屬性為true。
結(jié)論只涉及到了async和await語(yǔ)法糖生成的狀態(tài)機(jī)相關(guān),不涉及到關(guān)于異步或者同步相關(guān)的知識(shí)點(diǎn),因?yàn)檎f(shuō)到這些話題就變得很大了,還望諒解。
最近看到許多關(guān)于裁員跳槽甚至是換行的,每個(gè)人都有自己的生活,都有自己的處境,所以有些行為我們要換位思考,理解他們選擇生活的方式,每個(gè)人能得到自己想要的,能開(kāi)心就好,畢竟精力有限,為了最想要的總要舍棄一些。
相關(guān)文章
C#基于Socket的TCP通信實(shí)現(xiàn)聊天室案例
這篇文章主要為大家詳細(xì)介紹了C#基于Socket的TCP通信實(shí)現(xiàn)聊天室案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
Unity OnGUI實(shí)時(shí)顯示游戲FPS
這篇文章主要為大家詳細(xì)介紹了Unity OnGUI實(shí)時(shí)顯示游戲FPS,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
UGUI實(shí)現(xiàn)隨意調(diào)整Text中的字體間距
這篇文章主要為大家詳細(xì)介紹了UGUI實(shí)現(xiàn)隨意調(diào)整字體間距的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03
C#實(shí)現(xiàn)子窗體與父窗體通信方法實(shí)例總結(jié)
這篇文章主要介紹了C#實(shí)現(xiàn)子窗體與父窗體通信方法,實(shí)例總結(jié)了常用的四種窗體通信方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09
C# WPF開(kāi)源UI控件庫(kù)MaterialDesign介紹
這篇文章介紹了C# WPF開(kāi)源UI控件庫(kù)MaterialDesign,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-01-01
C#實(shí)現(xiàn)泛型List分組輸出元素的方法
這篇文章主要介紹了C#實(shí)現(xiàn)泛型List分組輸出元素的方法,涉及C#針對(duì)List的遍歷、排序、輸出等相關(guān)操作技巧,需要的朋友可以參考下2017-12-12

