天天看點

研究c#異步操作async await狀态機的總結

作者:opendotnet

前言

前一段時間得閑的時候優化了一下我之前的輪子[DotNetCoreRpc][1]小架構,其中主要的優化點主要是關于RPC異步契約調用的相關邏輯。在此過程中進一步了解了關于

async和await

異步操作相關的知識點,加深了異步操作的了解,是以總結一下。關于

async和await

每個人都有自己的了解,甚至關于

異步和同步

亦或者關于

異步和多線程

每個人也都有自己的了解。是以,如果本文涉及到個人觀點與您的觀點不一緻的時候請勿噴。結論固然重要,但是在這個過程中的引發的思考也很重要。

async await是文法糖

大家應該都比較清楚

async和await

這對關鍵字是一組文法糖,關于文法糖大家可以了解為,編碼過程中寫了一個關鍵字,但是編譯的時候會把它編譯成别的東西,主要是用來提升開發效率。比如我有一段關于

async和await

相關的代碼,如下所示

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#頂級語句聲明的,它是預設Main方法的,不過在編譯的時候編譯器會幫我們補齊Main方法,因為執行的時候

JIT

需要Main方法作為執行入口。關于如何檢視編譯後的代碼。我經常使用的是兩個工具,分别是

ILSpy

dnSpy

。這倆工具的差別在于

ILSpy

生成的代碼更清晰,

dnSpy

生成的源碼是可以直接調試的。需要注意的是如果使用的是

ILSpy

如果檢視文法糖本質的話,需要在

ILSpy

上選擇比文法糖版本低的版本,比如c# async和await關鍵字是在c# 5.0版本中引入的,是以我們這裡我們在

ILSpy

裡需要選擇c#4.0或以下版本,入下圖所示

研究c#異步操作async await狀态機的總結

如果使用的是

dnSpy

的話,需要在

調試-->選項-->反編譯器

中設定相關選項,如下所示

研究c#異步操作async await狀态機的總結

這樣就可以看到編譯後生成的代碼了。

生成的狀态機

圍繞上面的示例我這裡使用的

Debug模式

下編譯生成的dll使用的

ILSpy

進行反編譯,因為這裡我需要讓編譯的源碼看起來更清晰一點,而不是調試。如下所示首先看Main方法

//因為我們上面代碼var taskOne = await TaskOne()
//使用了await文法糖,是以被替換成了狀态機調用
[AsyncStateMachine(typeof(<<Main>gt;d__0))]
[DebuggerStepThrough]
private static Task <Main>$(string[] args)
{
//建立狀态機執行個體
 <<Main>gt;d__0 stateMachine = new <<Main>gt;d__0();
 stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
 stateMachine.args = args;
//設定狀态-1
 stateMachine.<>1__state = -1;
//啟動狀态機
 stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}

//這是系統預設幫我們生成的static void Main的入口方法
[SpecialName]
[DebuggerStepThrough]
private static void <Main>(string[] args)
{
//同步調用<Main>$方法
 <Main>$(args).GetAwaiter().GetResult();
}           

上面的代碼就是編譯器為我們生成的

Main

方法,通過這裡我們可以得到兩條資訊

  • 頂級語句

    編譯器會幫我們生成固定的入口函數格式,即

    static void Main

    這種标準格式
  • • 編譯器遇到

    await

    關鍵字則會編譯出一段狀态機相關的代碼,把我們的邏輯放到編譯的狀态機類裡

通過上面我們可以看到

<<Main>gt;d__0

這個類是編譯器幫我們生成的,我們可以看一下生成的代碼

[CompilerGenerated]
private sealed class <<Main>gt;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.able(new byte[] { 0, 1 })]
private TaskAwaiter<string> <>u__1;

private void MoveNext()
 {
int num = <>1__state;
try
 {
 TaskAwaiter<string> awaiter;
//num的值來自<>1__state,由于在建立狀态機的時候傳遞的是-1是以一定會走到這個邏輯
if (num != 0)
 {
//調用TaskOne方法,也就是上面我們寫的業務方法
//這個方法傳回的是TaskAwaiter<>執行個體,以為我們TaskOne方法是異步方法
 awaiter = <<Main>gt;g__TaskOne|0_0().GetAwaiter();
//判斷任務是否執行完成
if (!awaiter.IsCompleted)
 {
 num = (<>1__state = 0);
 <>u__1 = awaiter;
 <<Main>gt;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);
 }
//調用GetResult()方法擷取異步執行結果
 <>s__2 = awaiter.GetResult();
 <taskOne>5__1 = <>s__2;
 <>s__2 = ;
//這裡對應我們上面的輸出調用TaskOne方法的結果
 Console.WriteLine(<taskOne>5__1);
 Console.ReadLine();
 }
catch (Exception exception)
 {
 <>1__state = -2;
 <taskOne>5__1 = ;
 <>t__builder.SetException(exception);
return;
 }
 <>1__state = -2;
 <taskOne>5__1 = ;
 <>t__builder.SetResult();
 }

void IAsyncStateMachine.MoveNext()
 {
this.MoveNext();
 }

 [DebuggerHidden]
private void SetStateMachine([System.Runtime.CompilerServices.able(1)] IAsyncStateMachine stateMachine)
 {
 }

void IAsyncStateMachine.SetStateMachine([System.Runtime.CompilerServices.able(1)] IAsyncStateMachine stateMachine)
 {
this.SetStateMachine(stateMachine);
 }
}           

這裡的代碼可以看到編譯器生成的代碼,其實這就是對應上面我們寫的代碼

var taskOne = await TaskOne();
Console.WriteLine(taskOne);
Console.ReadLine();           

因為我們使用了

await

關鍵字,是以它幫我們生成了

IAsyncStateMachine

類,裡面的核心邏輯咱們待會在介紹,因為今天的主題

TaskOne

方法還沒介紹完成呢,

TaskOne

生成的代碼如下所示

//TaskOne方法編譯時生成的代碼
[CompilerGenerated]
private sealed class <<<Main>gt;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.able(new byte[] { 0, 1 })]
private TaskAwaiter<HttpResponseMessage> <>u__1;

 [System.Runtime.CompilerServices.able(new byte[] { 0, 1 })]
private TaskAwaiter<string> <>u__2;

private void MoveNext()
 {
int num = <>1__state;
string result;
try
 {
//因為我們使用了兩次await是以這裡會有兩個TaskAwaiter<>執行個體
//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;
 }
//這段邏輯針對的是我們手寫的這段代碼
//await ClassFactory.Client.GetAsync("https://www.cnblogs.com")
 awaiter2 = ClassFactory.Client.GetAsync("https://www.cnblogs.com").GetAwaiter();
//判斷任務是否完成
if (!awaiter2.IsCompleted)
 {
 num = (<>1__state = 0);
 <>u__1 = awaiter2;
 <<<Main>gt;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結果執行個體
 <>s__3 = awaiter2.GetResult();
 <httpResponse>5__1 = <>s__3;
 <>s__3 = ;
//這段代碼對應生成的則是await httpResponse.Content.ReadAsStringAsync()
 awaiter = <httpResponse>5__1.Content.ReadAsStringAsync().GetAwaiter();
if (!awaiter.IsCompleted)
 {
 num = (<>1__state = 1);
 <>u__2 = awaiter;
 <<<Main>gt;g__TaskOne|0_0>d stateMachine = this;
 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
 }
goto IL_0100;
 IL_0100:
//同步擷取httpResponse.Content.ReadAsStringAsync()放的結果
 <>s__4 = awaiter.GetResult();
 <content>5__2 = <>s__4;
 <>s__4 = ;
 result = <content>5__2;
 }
catch (Exception exception)
 {
 <>1__state = -2;
 <httpResponse>5__1 = ;
 <content>5__2 = ;
 <>t__builder.SetException(exception);
return;
 }
 <>1__state = -2;
 <httpResponse>5__1 = ;
 <content>5__2 = ;
//調用AsyncTaskMethodBuilder<>方法放置httpResponse.Content.ReadAsStringAsync()結果
 <>t__builder.SetResult(result);
 }

void IAsyncStateMachine.MoveNext()
 {
this.MoveNext();
 }

 [DebuggerHidden]
private void SetStateMachine([System.Runtime.CompilerServices.able(1)] IAsyncStateMachine stateMachine)
 {
 }

void IAsyncStateMachine.SetStateMachine([System.Runtime.CompilerServices.able(1)] IAsyncStateMachine stateMachine)
 {
this.SetStateMachine(stateMachine);
 }
}           

到這裡為止,這些方法就是編譯器幫我們生成的代碼,也就是這些代碼就在生成好的

dll

裡的。

啟動狀态機

接下來我們分析一下狀态機的調用過程,回到上面的

stateMachine.<>t__builder.Start(ref stateMachine)

這段狀态機啟動代碼,我們跟進去看一下裡面的邏輯

[MethodImpl(MethodImplOptions.AggressiveInlining)]
[DebuggerStepThrough]
public void Start<[able(0)] TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
//調用了AsyncMethodBuilderCore的Start方法并傳遞狀态機執行個體
//即<<Main>gt;d__0 stateMachine = new <<Main>gt;d__0()執行個體
 AsyncMethodBuilderCore.Start(ref stateMachine);
}

//AsyncMethodBuilderCore的Start方法
[DebuggerStepThrough]
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
if (stateMachine == )
 {
 ThrowHelper.ThrowArgumentException(ExceptionArgument.stateMachine);
 }
//擷取目前線程執行個體
 Thread currentThread = Thread.CurrentThread;
//擷取目前執行上下文
 ExecutionContext executionContext = currentThread._executionContext;
//擷取目前同步上下文
 SynchronizationContext synchronizationContext = currentThread._synchronizationContext;
try
 {
//調用狀态機的MoveNext方法
 stateMachine.MoveNext();
 }
finally
 {
//執行完MoveNext之後
//還原SynchronizationContext同步上下文到目前執行個體
if (synchronizationContext != currentThread._synchronizationContext)
 {
 currentThread._synchronizationContext = synchronizationContext;
 }
//還原ExecutionContext執行上下文到目前執行個體
 ExecutionContext executionContext2 = currentThread._executionContext;
if (executionContext != executionContext2)
 {
//執行完成之後把執行上下文裝載到目前線程
 ExecutionContext.RestoreChangedContextToThread(currentThread, executionContext, executionContext2);
 }
 }
}           

執行完異步任務之後,會判斷

SynchronizationContext

同步上下文環境和

ExecutionContext

執行上下文環境,保證異步異步之後的可以操作

UI線程

上的控件,或者異步的後續操作和之前的操作處在相同的執行上線文中。

題外話:ExecutionContext 是一個用于傳遞狀态和環境資訊的類,它可以在不同的執行上下文之間傳遞狀态。執行上下文表示代碼執行的環境,包括線程、應用程式域、安全上下文和調用上下文等。ExecutionContext 對象包含目前線程上下文的所有資訊,如目前線程的安全上下文、邏輯執行上下文、同步上下文和實體執行上下文等。它提供了方法,可以将目前的執行上下文複制到另一個線程中,或者在異步操作之間儲存和還原執行上下文。在異步程式設計中,使用 ExecutionContext 可以確定代碼在正确的上下文中運作,并且傳遞必要的狀态和環境資訊。

SynchronizationContext 是一個用于同步執行上下文和處理 UI 線程消息循環的抽象類。它可以将回調方法派發到正确的線程中執行,避免了跨線程通路的問題,并提高了應用程式的響應性和可靠性。在異步程式設計中,可以使用 SynchronizationContext.Current 屬性擷取目前線程的同步上下文,并使用同步上下文的 Post 或 Send 方法将回調方法派發到正确的線程中執行。

由于調用

stateMachine.<>t__builder.Start(ref stateMachine)

傳遞的是

new <<Main>gt;d__0()

執行個體,是以這裡核心就是在調用生成的狀态機

IAsyncStateMachine

執行個體,即我們上面的

<<Main>gt;d__0

類的

MoveNext()

方法

void IAsyncStateMachine.MoveNext()
{
this.MoveNext();
}           

由上面的代碼可知,本質是調用的私有的

MoveNext()

方法,即會執行我們真實邏輯的那個方法。由于編譯器生成的狀态機代碼的邏輯是大緻相同的,是以我們直接來看,我們業務具體落實的代碼即

<<<Main>gt;g__TaskOne|0_0>d

狀态機類裡的,私有的那個

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>gt;g__TaskOne|0_0>d stateMachine = this;
 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
return;
 }
//同步擷取異步結果
 <>s__4 = awaiter.GetResult();
}
else
{}

TaskAwaiter<string> awaiter;
//httpResponse.Content.ReadAsStringAsync()方法生成的邏輯
awaiter = <httpResponse>5__1.Content.ReadAsStringAsync().GetAwaiter();
//判斷任務是否完成
if (!awaiter.IsCompleted)
{
 num = (<>1__state = 1);
 <>u__2 = awaiter;
 <<<Main>gt;g__TaskOne|0_0>d stateMachine = this;
 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
//同步擷取異步結果,并将傳回值裝載
result= awaiter.GetResult();
<>t__builder.SetResult(result);           
當然這裡我們省了裡面的很多邏輯,為了讓結構看起來更清晰一點。 通過上面的它生成的結構來看,我們寫代碼的時候一個方法裡的每個await都會被生成一個

TaskAwaiter

邏輯,根據目前異步狀态

IsCompleted

判斷任務是否完成,來執行下一步操作。如果任務未完成

IsCompleted為false

則調用

AsyncTaskMethodBuilder

執行個體的

AwaitUnsafeOnCompleted

方法,如果異步已完成則直接擷取異步結果,進行下一步。

執行異步任務

通過上面的邏輯我們可以看到,如果異步任務沒有完成則調用了

AsyncTaskMethodBuilder

執行個體的

AwaitUnsafeOnCompleted

方法。接下來我們就看下

AwaitUnsafeOnCompleted

方法的實作

public void AwaitUnsafeOnCompleted<[able(0)] TAwaiter, [able(0)] TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
{
//調用AwaitUnsafeOnCompleted方法
 AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine, ref m_task);
}

internal static void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine, [Not] ref Task<TResult> taskField) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
{
//建立IAsyncStateMachineBox執行個體
 IAsyncStateMachineBox stateMachineBox = GetStateMachineBox(ref stateMachine, ref taskField);
//調用AwaitUnsafeOnCompleted()方法
 AwaitUnsafeOnCompleted(ref awaiter, stateMachineBox);
}

internal static void AwaitUnsafeOnCompleted<TAwaiter>(ref TAwaiter awaiter, IAsyncStateMachineBox box) where TAwaiter : ICriticalNotifyCompletion
{
//判斷awaiter執行個體類型
if (default(TAwaiter) !=  && awaiter is ITaskAwaiter)
 {
//擷取TaskAwaiter執行個體的m_task屬性即Task類型
 TaskAwaiter.UnsafeOnCompletedInternal(Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter).m_task, box, true);
return;
 }
if (default(TAwaiter) !=  && awaiter is IConfiguredTaskAwaiter)
 {
//與上面邏輯一緻m_task屬性即Task類型本質他們都在操作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) !=  && awaiter is IStateMachineBoxAwareAwaiter)
 {
try
 {
//調用IStateMachineBoxAwareAwaiter執行個體的AwaitUnsafeOnCompleted方法
 ((IStateMachineBoxAwareAwaiter)(object)awaiter).AwaitUnsafeOnCompleted(box);
return;
 }
catch (Exception exception)
 {
 System.Threading.Tasks.Task.ThrowAsync(exception, );
return;
 }
 }
try
 {
//調用ICriticalNotifyCompletion執行個體的UnsafeOnCompleted方法
 awaiter.UnsafeOnCompleted(box.MoveNextAction);
 }
catch (Exception exception2)
 {
 System.Threading.Tasks.Task.ThrowAsync(exception2, );
 }
}           

通過這個方法我們可以看到傳遞進來的TAwaiter都是

ICriticalNotifyCompletion

的實作類,是以他們的行為存在一緻性,隻是具體的實作動作根據不同的實作類型來判斷。

  • • 如果是

    ITaskAwaiter

    類的話直接調用

    TaskAwaiter.UnsafeOnCompletedInternal()

    方法,傳遞了

    TaskAwaiter.m_task

    屬性,這是一個

    Task

    類型的屬性
  • • 如果是

    IConfiguredTaskAwaiter

    類型的話,也是調用了

    TaskAwaiter.UnsafeOnCompletedInternal()

    方法,傳遞了

    ConfiguredTaskAwaiter.m_task

    屬性,這也是一個

    Task

    類型的屬性
  • • 如果是

    IStateMachineBoxAwareAwaiter

    類型的話,調用

    IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted()

    方法,傳遞的是目前的

    IAsyncStateMachineBox

    狀态機盒子執行個體,具體實作咱們待會看
  • • 如果上面的條件都不滿足的話,則調用

    ICriticalNotifyCompletion.UnsafeOnCompleted()

    方法,傳遞的是

    IAsyncStateMachineBox.MoveNextAction

    方法,

    IAsyncStateMachineBox

    實作類包裝了

    IAsyncStateMachine

    實作類,這裡的

    stateMachineBox.MoveNextAction

    本質是在執行

    IAsyncStateMachine的MoveNext

    的方法,即我們狀态機裡我們自己寫的業務邏輯。

我們首先來看一下

StateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted()

方法,找到一個實作類。因為它的實作類有好幾個,比如

ConfiguredValueTaskAwaiter

ValueTaskAwaiter

YieldAwaitable

等,這裡咱們選擇有類型的

ConfiguredValueTaskAwaiter

實作類,看一下

AwaitUnsafeOnCompleted

方法

void IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box)
{
object? obj = _value._obj;
 Debug.Assert(obj ==  || obj is Task || obj is IValueTaskSource);

if (obj is Task t)
 {
//如果是Task類型的話會調用TaskAwaiter.UnsafeOnCompletedInternal方法,也是上面咱們多次提到的
 TaskAwaiter.UnsafeOnCompletedInternal(t, box, _value._continueOnCapturedContext);
 }
else if (obj != )
 {
 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()

方法最終也是執行到了

TaskAwaiter.UnsafeOnCompletedInternal()

方法,這個咱們上面已經多次提到了。接下裡咱們再來看一下

ICriticalNotifyCompletion.UnsafeOnCompleted()

方法裡的實作是啥,咱們找到它的一個常用的實作類,也是咱們上面狀态機幫咱們生成的

TaskAwaiter<>

類裡的實作

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)
{
 ArgumentException.ThrowIf(continuation, "continuation");
if (TplEventSource.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
 {
 continuation = OutputWaitEtwEvents(task, continuation);
 }
//這裡調用了Task的SetContinuationForAwait方法
 task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext);
}           

咱們看到了這裡調用的是

Task的SetContinuationForAwait

方法,上面我們提到的

AwaitUnsafeOnCompleted

方法裡直接調用了

TaskAwaiter

UnsafeOnCompletedInternal

方法,咱們可以來看一下裡面的實作

internal static void UnsafeOnCompletedInternal(Task task, IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
{
if (TplEventSource.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
 {
//預設情況下我們是沒有去監聽EventSource釋出的時間消息
//如果你開啟了EventSource日志的監聽則會走到這裡
 task.SetContinuationForAwait(OutputWaitEtwEvents(task, stateMachineBox.MoveNextAction), continueOnCapturedContext, false);
 }
else
 {
 task.UnsafeSetContinuationForAwait(stateMachineBox, continueOnCapturedContext);
 }
}           

因為預設是沒有開啟

EventSource

的監聽,是以上面的兩個

TplEventSource.Log.IsEnabled

相關的邏輯執行不到,如果代碼裡堅挺了相關的

EventSource

則會執行這段邏輯。

SetContinuationForAwait

方法和

UnsafeSetContinuationForAwait

方法邏輯是一緻的,隻是因為如果開啟了

EventSource

的監聽會釋出事件消息,其中包裝了關于異步資訊的事件相關。是以我們可以直接來看

UnsafeSetContinuationForAwait

方法實作

internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
{
if (continueOnCapturedContext)
 {
//winform wpf等ui線程包含同步上下文SynchronizationContext相關的資訊
//如果存在則直接在SynchronizationContext同步上線文中的Post方法把異步結果在ui線程中完成回調執行
 SynchronizationContext current = SynchronizationContext.Current;
if (current !=  && current.GetType() != typeof(SynchronizationContext))
 {
 SynchronizationContextAwaitTaskContinuation synchronizationContextAwaitTaskContinuation = new SynchronizationContextAwaitTaskContinuation(current, stateMachineBox.MoveNextAction, false);
if (!AddTaskContinuation(synchronizationContextAwaitTaskContinuation, false))
 {
 synchronizationContextAwaitTaskContinuation.Run(this, false);
 }
return;
 }
//判斷是否包含内部任務排程器,如果不是預設的TaskScheduler.Default排程政策,也就是ThreadPoolTaskScheduler的方式執行MoveNext
//則使用TaskSchedulerAwaitTaskContinuation的Run方法執行MoveNext
 TaskScheduler internalCurrent = TaskScheduler.InternalCurrent;
if (internalCurrent !=  && internalCurrent != TaskScheduler.Default)
 {
 TaskSchedulerAwaitTaskContinuation taskSchedulerAwaitTaskContinuation = new TaskSchedulerAwaitTaskContinuation(internalCurrent, stateMachineBox.MoveNextAction, false);
if (!AddTaskContinuation(taskSchedulerAwaitTaskContinuation, false))
 {
 taskSchedulerAwaitTaskContinuation.Run(this, false);
 }
return;
 }
 }
//執行兜底邏輯使用線程池執行
if (!AddTaskContinuation(stateMachineBox, false))
 {
 ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, true);
 }
}           

上面我們提到過

IAsyncStateMachineBox

實作類包裝了

IAsyncStateMachine

實作類,它的

stateMachineBox.MoveNextAction

本質是在執行

AsyncStateMachine的MoveNext

的方法,即我們狀态機裡的自己的業務邏輯。根據上面的邏輯我們來大緻總結一下相關的執行政策

  • • 如果包含

    SynchronizationContext

    同步上下文,也就是winform wpf等ui線程,則直接在SynchronizationContext同步上線文中的Post方法把異步結果在ui線程中完成回調執行,裡面的核心方法咱們待會會看到
  • • 如果

    TaskScheduler

    排程器不是預設的

    ThreadPoolTaskScheduler

    排程器,則使用自定義的TaskScheduler來執行MoveNext方法,統一裡面的核心方法咱們待來看
  • • 兜底的邏輯則是使用線程池來執行,即使用

    ThreadPool的UnsafeQueueUserWorkItemInternal

    方法

好了上面留下了兩個核心的方法,沒有展示相關的實作,首先咱們來看下

TaskSchedulerAwaitTaskContinuation的Run

方法,這個方法适用于存在同步上下文的場景,來看下它的核心邏輯

internal sealed override void Run(Task task, bool canInlineContinuationTask)
{
//判斷目前線程同步上下文是否和傳遞的同步上下文一緻,則直接執行,說明目前線程可以直接使用異步結果
if (canInlineContinuationTask && m_syncContext == SynchronizationContext.Current)
 {
 RunCallback(AwaitTaskContinuation.GetInvokeActionCallback(), m_action, ref Task.t_currentTask);
return;
 }
//如果不是同一個同步上下文則執行PostAction委托
 RunCallback(PostAction, this, ref Task.t_currentTask);
}

private static void PostAction(object state)
{
//通過傳遞的state來捕獲執行回調的同步上下文,這裡使用的SynchronizationContext的非阻塞的Post方法來執行後續邏輯
 SynchronizationContextAwaitTaskContinuation synchronizationContextAwaitTaskContinuation = (SynchronizationContextAwaitTaskContinuation)state;
 synchronizationContextAwaitTaskContinuation.m_syncContext.Post(s_postCallback, synchronizationContextAwaitTaskContinuation.m_action);
}

protected void RunCallback(ContextCallback callback, object state, ref Task currentTask)
{
//捕獲執行上下文,異步執行完成之後在執行上下文中執行後續邏輯
 ExecutionContext capturedContext = m_capturedContext;
if (capturedContext == )
 {
//核心邏輯就是再行上面的委托即AwaitTaskContinuation.GetInvokeActionCallback方法或PostAction方法
 callback(state);
 }
else
 {
 ExecutionContext.RunInternal(capturedContext, callback, state);
 }
}           

上面的方法省略了一些邏輯,為了讓邏輯看起來更清晰,我們可以看到裡面的邏輯,即在同步上下文

SynchronizationContext

中執行異步的回調的結果。如果目前線程就包含同步上下文則直接執行,如果不是則使用之前傳遞進來的同步上下文來執行。執行的時候會嘗試捕獲執行上下文。咱們還說到了如果

TaskScheduler

排程器不是預設的

ThreadPoolTaskScheduler

排程器,則使用自定義的TaskScheduler來執行MoveNext方法,來看下裡面的核心實作

internal sealed override void Run(Task ignored, bool canInlineContinuationTask)
{
//如果目前的scheduler政策是TaskScheduler.Default即預設的ThreadPoolTaskScheduler
//則直接使用預設政策排程任務
if (m_scheduler == TaskScheduler.Default)
 {
base.Run(ignored, canInlineContinuationTask);
return;
 }
//如果不是預設政策則使用,我們定義的TaskScheduler
 Task task = CreateTask(delegate(object state)
 {
try
 {
 ((Action)state)();
 }
catch (Exception exception)
 {
 Task.ThrowAsync(exception, );
 }
 }, m_action, m_scheduler);//這裡的m_scheduler指的是自定義的TaskScheduler
bool flag = canInlineContinuationTask && (TaskScheduler.InternalCurrent == m_scheduler || Thread.CurrentThread.IsThreadPoolThread);
//或者是task其他形式的政策執行
if (flag)
 {
 TaskContinuation.InlineIfPossibleOrElseQueue(task, false);
return;
 }
try
 {
 task.ScheduleAndStart(false);
 }
catch (TaskSchedulerException)
 {
 }
}           

這個邏輯看起來比較清晰,即根據Task的執行政策

TaskScheduler

判斷如何執行任務,比如預設的

ThreadPoolTaskScheduler

政策,或其他政策,比如單線程政策或者自定義的等等。 上面的執行過程可以總結為以下兩點

  • • 是否是

    Task

    排程,否則執行預設的

    ThreadPool.UnsafeQueueUserWorkItemInternal()

    執行。如果是

    TaskScheduler

    則判斷是哪一種政策,比如是預設的

    ThreadPoolTaskScheduler

    或是其它政策亦或是自定義政策等。
  • • 是否包含同步上下文

    SynchronizationContext

    ,比如UI線程,大家都知道修改界面控件需要在UI線程上才能執行,但是

    await

    操作可能存線上程切換如果await的結果需要在UI展示需要同步上下文保證異步的結果在UI線程中執行。

線程池和Task關聯

如果任務需要執行中,我們總得想辦法把結果給相應的

Task

執行個體,這樣我們才能在執行完成之後把得到對應的執行狀态或者執行結果在相關的Task中展現出來,友善我們判斷Task是否執行完成或者擷取相關的執行結果,在

ThreadPoolWorkQueue

中有相關的邏輯具體在

DispatchWorkItem

方法中

private static void DispatchWorkItem(object workItem, Thread currentThread)
{
//判斷線上程池中自行的任務書否是Task任務
 Task task = workItem as Task;
if (task != )
 {
 task.ExecuteFromThreadPool(currentThread);
 }
else
 {
 Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
 }
}           

ThreadPool

裡的線程執行了

Task的ExecuteWithThreadLocal

的方法,核心執行方法在

Task的ExecuteWithThreadLocal

,這樣的話執行相關的結果就可以展現在

Task

執行個體中,比如

Task的IsCompleted

屬性判斷是否執行完成,或者

Task<TResult>的GetResult

f方法擷取結果等等。

Task的FromResult

這裡需要注意的是

Task.FromResult<TResult>(TResult)

這個方法,相信大家經常用到,如果你的執行結果需要包裝成

Task<TResult>

總會用到這個方法。它的意思是

建立一個Task<TResult>,并以指定結果成功完成。

,也就是

Task<TResult>的IsCompleted屬性為true

,這個結論可以在

dotnet api

中Task.FromResult(TResult)[2]文檔中看到,因為我們隻需要把我們已有的結果包裝成Task是以不涉及到複雜的執行,這也意味着在生成狀态機的時候

MoveNext

方法裡的邏輯判斷

IsCompleted

時候代表任務是直接完成的,會直接通過

GetResult()

擷取到結果,不需要

AwaitUnsafeOnCompleted

去根據執行政策執行

private void MoveNext()
{
int num = <>1__state;
try
 {
 TaskAwaiter<string> awaiter;
if (num != 0)
 {
 awaiter = Task.FromResult("Hello World").GetAwaiter();
//這裡的IsCompleted會為true不會執行相關的執行政策
if (!awaiter.IsCompleted)
 {
 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
 }
 }
else
 {
 }
 <>s__2 = awaiter.GetResult();
 }
catch (Exception exception)
 {
 }
 <>t__builder.SetResult();
}           

總結

本文主要是展示了近期對

async和await

生成的狀态機的研究,大概了解了相關的執行過程。由于異步程式設計涉及到的東西比較多,而且相當複雜,足夠寫一本書。是以本文設計到的不過是一些皮毛,也由于本人能力有限了解的不一定對,還望諒解。通過本文大家知道

async和await

是文法糖,會生成狀态機相關代碼,讓我們來總結一下

  • • 首先

    async和await

    是文法糖,會生成狀态機類并填充我們編寫的業務代碼相關
  • • 如果是未完成任務也就是

    IsCompleted為false

    則會執行相關的邏輯去執行任務
    • • 是否是

      Task

      排程,否則執行預設的

      ThreadPool.UnsafeQueueUserWorkItemInternal()

      執行。如果是

      TaskScheduler

      則判斷是哪一種政策,比如是預設的

      ThreadPoolTaskScheduler

      或是其它政策亦或是自定義政策等。
    • • 是否包含同步上下文

      SynchronizationContext

      ,比如UI線程,大家都知道修改界面控件需要在UI線程上才能執行,但是

      await

      操作可能存線上程切換如果await的結果需要在UI展示需要同步上下文保證異步的結果在UI線程中執行。
  • • 需要注意的是

    Task.FromResult<TResult>(TResult)

    這個方法,它的意思是

    建立一個Task<TResult>,并以指定結果成功完成。

    ,也就是

    Task<TResult>的IsCompleted屬性為true

結論隻涉及到了

async和await

文法糖生成的狀态機相關,不涉及到關于異步或者同步相關的知識點,因為說到這些話題就變得很大了,還望諒解。

最近看到許多關于裁員跳槽甚至是換行的,每個人都有自己的生活,都有自己的處境,是以有些行為我們要換位思考,了解他們選擇生活的方式,每個人能得到自己想要的,能開心就好,畢竟精力有限,為了最想要的總要舍棄一些。

引用連結

[1]

[DotNetCoreRpc]: https://github.com/softlgl/DotNetCoreRpc

[2]

Task.FromResult(TResult): https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.fromresult?view=net-7.0

繼續閱讀