研究c#非同步操作async await狀態機的總結

yi念之間發表於2023-02-22

前言

    前一段時間得閒的時候最佳化了一下我之前的輪子[DotNetCoreRpc]小框架,其中主要的最佳化點主要是關於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方法作為執行入口。關於如何檢視編譯後的程式碼。我經常使用的是兩個工具,分別是ILSpydnSpy。這倆工具的區別在於ILSpy生成的程式碼更清晰,dnSpy生成的原始碼是可以直接除錯的。需要注意的是如果使用的是ILSpy如果檢視語法糖本質的話,需要在ILSpy上選擇比語法糖版本低的版本,比如c# async和await關鍵字是在c# 5.0版本中引入的,所以我們這裡我們在ILSpy裡需要選擇c#4.0或以下版本,入下圖所示如果使用的是dnSpy的話,需要在除錯-->選項-->反編譯器中設定相關選項,如下所示這樣就可以看到編譯後生成的程式碼了。

生成的狀態機

圍繞上面的示例我這裡使用的Debug模式下編譯生成的dll使用的ILSpy進行反編譯,因為這裡我需要讓編譯的原始碼看起來更清晰一點,而不是除錯。如下所示首先看Main方法

//因為我們上面程式碼var taskOne = await TaskOne()
//使用了await語法糖,所以被替換成了狀態機呼叫
[AsyncStateMachine(typeof(<<Main>$>d__0))]
[DebuggerStepThrough]
private static Task <Main>$(string[] args)
{
	//建立狀態機例項
	<<Main>$>d__0 stateMachine = new <<Main>$>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>$>d__0 這個類是編譯器幫我們生成的,我們可以看一下生成的程式碼

[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的值來自<>1__state,由於在建立狀態機的時候傳遞的是-1所以一定會走到這個邏輯
			if (num != 0)
			{
				//呼叫TaskOne方法,也就是上面我們寫的業務方法
				//這個方法返回的是TaskAwaiter<>例項,以為我們TaskOne方法是非同步方法
				awaiter = <<Main>$>g__TaskOne|0_0().GetAwaiter();
				//判斷任務是否執行完成
				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);
			}
			//呼叫GetResult()方法獲取非同步執行結果
			<>s__2 = awaiter.GetResult();
			<taskOne>5__1 = <>s__2;
			<>s__2 = null;
			//這裡對應我們上面的輸出呼叫TaskOne方法的結果
			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);
	}
}

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

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

因為我們使用了await關鍵字,所以它幫我們生成了IAsyncStateMachine類,裡面的核心邏輯我們們待會在介紹,因為今天的主題TaskOne方法還沒介紹完成呢,TaskOne生成的程式碼如下所示

//TaskOne方法編譯時生成的程式碼
[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
		{
			//因為我們使用了兩次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>$>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 = null;
			//這段程式碼對應生成的則是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()放的結果
			<>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;
		//呼叫AsyncTaskMethodBuilder<>方法放置httpResponse.Content.ReadAsStringAsync()結果
		<>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裡的。

啟動狀態機

接下來我們分析一下狀態機的呼叫過程,回到上面的stateMachine.<>t__builder.Start(ref stateMachine)這段狀態機啟動程式碼,我們跟進去看一下里面的邏輯

[MethodImpl(MethodImplOptions.AggressiveInlining)]
[DebuggerStepThrough]
public void Start<[Nullable(0)] TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
	//呼叫了AsyncMethodBuilderCore的Start方法並傳遞狀態機例項
	//即<<Main>$>d__0 stateMachine = new <<Main>$>d__0()例項
	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);
	}
	//獲取當前執行緒例項
	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>$>d__0()例項,所以這裡核心就是在呼叫生成的狀態機IAsyncStateMachine例項,即我們上面的<<Main>$>d__0類的MoveNext()方法

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

由上面的程式碼可知,本質是呼叫的私有的MoveNext()方法,即會執行我們真實邏輯的那個方法。由於編譯器生成的狀態機程式碼的邏輯是大致相同的,所以我們直接來看,我們業務具體落實的程式碼即<<<Main>$>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>$>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>$>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<[Nullable(0)] TAwaiter, [Nullable(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, [NotNull] 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) != null && awaiter is ITaskAwaiter)
	{
		//獲取TaskAwaiter例項的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型別本質他們都在操作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
		{
			//呼叫IStateMachineBoxAwareAwaiter例項的AwaitUnsafeOnCompleted方法
			((IStateMachineBoxAwareAwaiter)(object)awaiter).AwaitUnsafeOnCompleted(box);
			return;
		}
		catch (Exception exception)
		{
			System.Threading.Tasks.Task.ThrowAsync(exception, null);
			return;
		}
	}
	try
	{
		//呼叫ICriticalNotifyCompletion例項的UnsafeOnCompleted方法
		awaiter.UnsafeOnCompleted(box.MoveNextAction);
	}
	catch (Exception exception2)
	{
		System.Threading.Tasks.Task.ThrowAsync(exception2, null);
	}
}

透過這個方法我們可以看到傳遞進來的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()方法,找到一個實現類。因為它的實現類有好幾個,比如ConfiguredValueTaskAwaiterValueTaskAwaiterYieldAwaitable等,這裡我們們選擇有型別的ConfiguredValueTaskAwaiter實現類,看一下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型別的話會呼叫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()方法最終也是執行到了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)
{
	ArgumentNullException.ThrowIfNull(continuation, "continuation");
	if (TplEventSource.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
	{
		continuation = OutputWaitEtwEvents(task, continuation);
	}
	//這裡呼叫了Task的SetContinuationForAwait方法
	task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext);
}

我們們看到了這裡呼叫的是Task的SetContinuationForAwait方法,上面我們提到的AwaitUnsafeOnCompleted方法裡直接呼叫了TaskAwaiterUnsafeOnCompletedInternal方法,我們們可以來看一下里面的實現

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 != null && 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 != null && 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 == null)
	{
		//核心邏輯就是再行上面的委託即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, null);
		}
	}, 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 != null)
	{
		task.ExecuteFromThreadPool(currentThread);
	}
	else
	{
		Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
	}
}

ThreadPool裡的執行緒執行了Task的ExecuteWithThreadLocal的方法,核心執行方法在Task的ExecuteWithThreadLocal,這樣的話執行相關的結果就可以體現在Task例項中,比如Task的IsCompleted屬性判斷是否執行完成,或者Task<TResult>的GetResultf方法獲取結果等等。

Task的FromResult

這裡需要注意的是Task.FromResult<TResult>(TResult)這個方法,相信大家經常用到,如果你的執行結果需要包裝成Task<TResult>總會用到這個方法。它的意思是建立一個Task<TResult>,並以指定結果成功完成。,也就是Task<TResult>的IsCompleted屬性為true,這個結論可以在dotnet apiTask.FromResult(TResult)文件中看到,因為我們只需要把我們已有的結果包裝成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語法糖生成的狀態機相關,不涉及到關於非同步或者同步相關的知識點,因為說到這些話題就變得很大了,還望諒解。

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

?歡迎掃碼關注我的公眾號? 研究c#非同步操作async await狀態機的總結

相關文章