【原創】 本文只是個人筆記,很多錯誤,歡迎指出。
環境:vs2022 .net6.0 C#10
參考:https://blog.csdn.net/brook_shi/article/details/50803957
Await 就像一個一元運算符:它接受一個參數,一個可等待的("awaitable"是一個異步操作)
使用場景:1、首次顯示頁面/表單時,需要將其同步初始化為一種"正在加載"狀態,然后啟動異步操作以檢索所需的數據。稍后,當數據到達時,更新現有頁面/表單以顯示處於"已加載"狀態的數據。同樣適用與wpf開發。
異步編程
異步編程就是使用future模式(又稱promise)或者回調機制來實現(Non-blocking on waiting)。
如果使用回調或事件來實現(容易callback hell),不僅編寫這樣的代碼不直觀,很快就容易把代碼搞得一團糟。不過在.NET 4.5(C# 5)中引入的async/await關鍵字(在.NET 4.0中通過添加Microsoft.Bcl.Async包也可以使用),讓編寫異步代碼變得容易和優雅。通過使用async/await關鍵字,可以像寫同步代碼那樣編寫異步代碼,所有的回調和事件處理都交給編譯器和運行時幫你處理了。
使用異步編程有兩個好處:不阻塞主線程(比如UI線程),提高服務端應用的吞吐量。所以微軟推薦ASP.NET中默認使用異步來處理請求。
介紹關鍵Async 和 Await
異步方法看起來像這樣:
public async Task DoSomethingAsync() { // In the Real World, we would actually do something... // For this example, we're just going to (asynchronously) wait 100ms. await Task.Delay(100); }
“async”關鍵字啟用該方法中的“await”關鍵字,並讓編譯器改變該方法的處理方式,生成一個異步狀態機類,將方法內部代碼剪切到狀態機類的內部,這就是async關鍵字所做的一切!它不會在線程池線程上運行此方法。async關鍵字只啟用await關鍵字(並管理方法結果)。
異步方法的開始就像其他方法一樣執行。也就是說,它同步運行,直到遇到“await”(或拋出異常)。
await關鍵字是可以實現異步的地方。Await就像一個一元操作符:它只接受一個參數,一個awaitable(“awaitable”是一個異步操作)。Await檢查Await,看看它是否已經完成;如果awaitable已經完成,那么該方法將繼續運行(同步運行,就像常規方法一樣)。
如果“await”看到awaitable還沒有完成,那么它將采取異步操作。它告訴awaitable在完成時運行該方法的剩余部分,然后從async方法返回。
稍后,當awaitable完成時,它將執行async方法的其余部分。如果你正在等待一個內置的awaitable(比如一個任務),那么async方法的其余部分將在“await”返回之前捕獲的“上下文”上執行。
我喜歡將“等待”視為“異步等待”。也就是說,async方法會暫停直到awaitable完成(所以它會等待),但實際的線程沒有被阻塞(所以它是異步的)。
等待者模式
Await/Async 異步使模式使用的是等待者模式。等待者模式要求等待者公開IsCompleted屬性,GetResult
方法和OnCompleted
方法(可選地帶有UnsafeOnCompleted
方法,unsafe表示不使用可執行上下 回導致漏洞)。
Awaitable-可等待類型
正如我所提到的,"await"采用單個參數 - 一個"awaitable" - 這是一個異步操作。.NET 框架中已經有兩種常見的可等待類型:Task<T> 和 Task。
特殊方法(如"Task.Yield")返回YieldAwaitabl
e可等待對象,並且 WinRT 運行時(在 Windows 8 中提供)具有非托管的可等待類型。
您還可以創建自己的可等待類型(通常出於性能原因),或使用擴展方法使不可等待的類型可等待。
關於 awaitables 的一個要點是:它是可等待的類型,而不是返回該類型的方法。換句話說,您可以等待返回 Task ...因為該方法返回 Task,而不是因為它是異步的。因此,您也可以等待返回 Task 的非異步方法的結果:
public async Task NewStuffAsync() { // 異步方法 await ... } public Task MyOldTaskParallelLibraryCode() { // 不是異步方法 ... } public async Task ComposeAsync() { // We can await Tasks, regardless of where they come from. await NewStuffAsync(); await MyOldTaskParallelLibraryCode(); }
返回類型
異步方法可以返回 Task<T>、Task 或 void。在幾乎所有情況下,您都希望返回 Task<T> 或 Task,並且僅在必要時返回 void。
為什么要返回 Task<T> 或 Task?因為它們是可以等待的,而虛空不是。因此,如果您有一個異步方法返回 Task<T> 或 Task,則可以將結果傳遞給等待。使用void方法,您無需等待任何東西。
當您具有異步事件處理程序時,必須返回 void。
返回值
返回 Task 或 void 的異步方法沒有返回值。返回 Task<T> 的異步方法必須返回 T 類型的值:
public async Task<int> CalculateAnswer() { await Task.Delay(100); // (Probably should be longer...) // Return a type of "int", not "Task<int>" return 42; }
這有點奇怪,但這種設計背后有很好的理由。
問:為什么我不能寫這個:
// Hypothetical syntax public async int GetValue() { await TaskEx.Delay(100); return 13; // Return type is "int" }
考慮一下:方法簽名對調用方的外觀如何?返回值的類型為是可等待的對象,那么我們對異步方法調用時候可以給該方法或對象標注成await。如果其他類型我們將很難判斷該方法是否可以標注成await。
上下文
在概述中,我提到,當您等待內置的可等待對象時,等待對象將捕獲當前的"上下文",然后將其應用於異步方法的其余部分。這個"背景"到底是什么?
簡單的答案:
- 如果你位於 UI 線程上,則它是 UI 上下文。
- 如果您正在響應 ASP.NET 請求,則這是一個 ASP.NET 請求上下文。
- 否則,它通常是線程池上下文。
復雜的答案:
- 如果 SynchronizationContext.Current 不為 null,則它是當前的 SyncContext。(UI 和 ASP.NET 請求上下文是同步上下文上下文)。
- 否則,它是當前的 TaskScheduler(TaskScheduler.Default 是線程池上下文)。
這在現實世界中意味着什么?首先,捕獲(和還原)UI/ASP.NET 上下文是透明完成的:
// WinForms example (it works exactly the same for WPF). private async void DownloadFileButton_Click(object sender, EventArgs e) { // Since we asynchronously wait, the UI thread is not blocked by the file download. await DownloadFileAsync(fileNameTextBox.Text); // Since we resume on the UI context, we can directly access UI elements. resultTextBox.Text = "File downloaded!"; } // ASP.NET example protected async void MyButton_Click(object sender, EventArgs e) { // Since we asynchronously wait, the ASP.NET thread is not blocked by the file download. // This allows the thread to handle other requests while we're waiting. await DownloadFileAsync(...); // Since we resume on the ASP.NET context, we can access the current request. // We may actually be on another *thread*, but we have the same ASP.NET request context. Response.Write("File downloaded!"); }
這對於事件處理程序非常有用,但事實證明,它不是您希望大多數其他代碼(實際上,您將要編寫的大多數異步代碼)所需要的。
避免上下文
大多數情況下,您不需要同步回"主要"上下文。大多數異步方法在設計時都會考慮組合:它們等待其他操作,每個操作本身都表示一個異步操作(可以由其他操作組成)。在這種情況下,您希望通過調用 ConfigureAwait 並傳遞 false 來告訴等待者不要捕獲當前上下文,例如:
private async Task DownloadFileAsync(string fileName) { // Use HttpClient or whatever to download the file contents. var fileContents = await DownloadFileContentsAsync(fileName).ConfigureAwait(false); // Note that because of the ConfigureAwait(false), we are not on the original context here. // Instead, we're running on the thread pool. // Write the file contents out to a disk file. await WriteToDiskAsync(fileName, fileContents).ConfigureAwait(false); // The second call to ConfigureAwait(false) is not *required*, but it is Good Practice. } // WinForms example (it works exactly the same for WPF). private async void DownloadFileButton_Click(object sender, EventArgs e) { // Since we asynchronously wait, the UI thread is not blocked by the file download. await DownloadFileAsync(fileNameTextBox.Text); // Since we resume on the UI context, we can directly access UI elements. resultTextBox.Text = "File downloaded!"; }
此示例需要注意的重要一點是,異步方法調用的每個"級別"都有自己的上下文。DownloadFileButton_Click在 UI 上下文中啟動,名為 DownloadFileAsync。DownloadFileAsync 也在 UI 上下文中啟動,但隨后通過調用 ConfigureAwait(false) 退出了其上下文。下載文件異步的其余部分在線程池上下文中運行。但是,當 DownloadFileAsync 完成並DownloadFileButton_Click恢復時,它會在 UI 上下文中恢復。
一個好的經驗法則是使用 ConfigureAwait(false),除非您知道確實需要上下文。
異步組合
到目前為止,我們只考慮了串行組合:異步方法一次等待一個操作。也可以啟動多個操作並等待其中一個(或所有)完成。您可以通過啟動操作來執行此操作,但直到稍后才等待它們:
public async Task DoOperationsConcurrentlyAsync() { Task[] tasks = new Task[3]; tasks[0] = DoOperation0Async(); tasks[1] = DoOperation1Async(); tasks[2] = DoOperation2Async(); // At this point, all three tasks are running at the same time. // Now, we await them all. await Task.WhenAll(tasks); } public async Task<int> GetFirstToRespondAsync() { // Call two web services; take the first response. Task<int>[] tasks = new[] { WebService1Async(), WebService2Async() }; // Await for the first one to respond. Task<int> firstTask = await Task.WhenAny(tasks); // Return the result. return await firstTask; }
通過使用並發組合(Task.WhenAll 或 Task.WhenAny),可以執行簡單的並發操作。您還可以將這些方法與 Task.Run 一起使用,以進行簡單的並行計算。但是,這不能替代任務並行庫 - 任何高級 CPU 密集型並行操作都應使用 TPL 完成。
異步的使用心得
當你省略await/async時候異常的報錯方式也會不一樣。
所以你當你理解異步原理后,還是建議不要省略await/async。
Task 無疑是一個現代 OVERLAPPED 抽象。它表示一些工作,通常是 I/O 密集型的,可以由 CPU 之外的某些工作完成,並且當該工作完成時,任務將收到通知。
事件處理程序是從同步角度設計的,這就是為什么將它們與異步一起使用是很尷尬的原因。
async\await的代碼運行順序
通過分析一段代碼運行來了解async\await的運作機理(本案例是控制台應用程序)
namespace MyTask; class Program { public static void Main(string[] args) { Task<int> baconTask = GetUrlContentLengthAsync(3); baconTask.ContinueWith(t => Console.WriteLine(t.Result)); Console.Read(); } static async Task<int> GetUrlContentLengthAsync(int slices) { HttpClient httpClient = new(); //********* 也可以合並起來寫*********// // string content = await httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");
//將生成TaskAwaiter的等待器,該等待器繼承 ITaskAwaiter Task<string> getContent= httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html"); string content = await getContent; //await getContent反編譯后是awaiter = <getContent>5__2.GetAwaiter(); DoInDependentWork(); return content.Length; } static void DoInDependentWork() { Console.WriteLine($"content.Length:Working"); } }
備用 改進后的代碼,輸出線程的變化

using System.Runtime.CompilerServices; namespace MyTask; class Program { public static void Main(string[] args) { Console.WriteLine("mian方法在異步方法 標志之前的線程:{0}", Environment.CurrentManagedThreadId); Task<int> baconTask = GetUrlContentLengthAsync(3); // baconTask.ContinueWith(t => Console.WriteLine(t.Result)); baconTask.Wait(); Console.WriteLine("mian方法在異步方法 標志之后的線程:{0}", Environment.CurrentManagedThreadId); Console.Read(); } static async Task<int> GetUrlContentLengthAsync(int slices) { Console.WriteLine("異步方法在await 標志之前的線程:{0}", Environment.CurrentManagedThreadId); HttpClient httpClient = new(); // string content = await httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html"); Task<string> getContent = httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html"); string content = await getContent; //await getContent反編譯后是awaiter = <getContent>5__2.GetAwaiter(); Console.WriteLine("異步方法在await 標志之后的線程:{0}",Environment.CurrentManagedThreadId); return content.Length; } static void DoInDependentWork() { Console.WriteLine($"content.Length:Working"); } } /* 輸出 mian方法在異步方法 標志之前的線程:1 異步方法在await 標志之前的線程:1 異步方法在await 標志之后的線程:10 mian方法在異步方法 標志之后的線程:1 */
IL反編譯后的代碼

// MyTask.Program using System; using System.Diagnostics; using System.Net.Http; using System.Runtime.CompilerServices; using System.Threading.Tasks; using MyTask; [System.Runtime.CompilerServices.NullableContext(1)] [System.Runtime.CompilerServices.Nullable(0)] internal class Program { [Serializable] [CompilerGenerated] private sealed class <>c { [System.Runtime.CompilerServices.Nullable(0)] public static readonly <>c <>9 = new <>c(); [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] public static Action<Task<int>> <>9__0_0; internal void <Main>b__0_0(Task<int> t) { Console.WriteLine(t.Result); } } [CompilerGenerated] private sealed class <GetUrlContentLengthAsync>d__1 : IAsyncStateMachine { public int <>1__state; [System.Runtime.CompilerServices.Nullable(0)] public AsyncTaskMethodBuilder<int> <>t__builder; public int slices; [System.Runtime.CompilerServices.Nullable(0)] private HttpClient <httpClient>5__1; [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] private Task<string> <getContent>5__2; [System.Runtime.CompilerServices.Nullable(0)] private string <content>5__3; [System.Runtime.CompilerServices.Nullable(0)] private string <>s__4; [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })] private TaskAwaiter<string> <>u__1; private void MoveNext() { int num = <>1__state; int length; try { TaskAwaiter<string> awaiter; if (num != 0) { <httpClient>5__1 = new HttpClient(); <getContent>5__2 = <httpClient>5__1.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html"); awaiter = <getContent>5__2.GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <GetUrlContentLengthAsync>d__1 stateMachine = this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } } else { awaiter = <>u__1; <>u__1 = default(TaskAwaiter<string>); num = (<>1__state = -1); } <>s__4 = awaiter.GetResult(); <content>5__3 = <>s__4; <>s__4 = null; DoInDependentWork(); length = <content>5__3.Length; } catch (Exception exception) { <>1__state = -2; <httpClient>5__1 = null; <getContent>5__2 = null; <content>5__3 = null; <>t__builder.SetException(exception); return; } <>1__state = -2; <httpClient>5__1 = null; <getContent>5__2 = null; <content>5__3 = null; <>t__builder.SetResult(length); } void IAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); } [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } } public static void Main(string[] args) { Task<int> baconTask = GetUrlContentLengthAsync(3); baconTask.ContinueWith(<>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Action<Task<int>>(<>c.<>9.<Main>b__0_0))); Console.Read(); } [AsyncStateMachine(typeof(<GetUrlContentLengthAsync>d__1))] [DebuggerStepThrough] private static Task<int> GetUrlContentLengthAsync(int slices) { <GetUrlContentLengthAsync>d__1 stateMachine = new <GetUrlContentLengthAsync>d__1(); stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create(); stateMachine.slices = slices; stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; } private static void DoInDependentWork() { Console.WriteLine("content.Length:Working"); } }
通過IL代碼我們可以分析出整個代碼運行過程 如下:
關系圖中的數字對應於以下步驟,在調用方法調用異步方法時啟動。
-
Main調用方法調用並等待
GetUrlContentLengthAsync
異步方法。 -
GetUrlContentLengthAsync
可創建 HttpClient 實例並調用 GetStringAsync 異步方法以下載網站內容作為字符串。 -
GetStringAsync
中發生了某種情況,該情況掛起了它的進程。 可能必須等待網站下載或一些其他阻止活動。 為避免阻止資源,GetStringAsync
會將控制權出讓給其調用方GetUrlContentLengthAsync
。GetStringAsync
返回 Task<TResult>,其中TResult
為字符串,並且GetUrlContentLengthAsync
將任務分配給getStringTask
變量。 該任務表示調用GetStringAsync
的正在進行的進程,其中承諾當工作完成時產生實際字符串值。 -
由於尚未等待
getStringTask
,因此,GetUrlContentLengthAsync
可以繼續執行不依賴於GetStringAsync
得出的最終結果的其他工作。 該任務由對同步方法DoIndependentWork
的調用表示。 -
DoIndependentWork
是完成其工作並返回其調用方的同步方法。 -
GetUrlContentLengthAsync
已運行完畢,可以不受getStringTask
的結果影響。 接下來,GetUrlContentLengthAsync
需要計算並返回已下載的字符串的長度,但該方法只有在獲得字符串的情況下才能計算該值。因此,
GetUrlContentLengthAsync
使用一個 await 運算符來掛起其進度,並把控制權交給調用GetUrlContentLengthAsync
的方法。GetUrlContentLengthAsync
將Task<int>
返回給調用方。 該任務表示對產生下載字符串長度的整數結果的一個承諾。備注
如果
GetStringAsync
(因此getStringTask
)在GetUrlContentLengthAsync
等待前完成,則控制會保留在GetUrlContentLengthAsync
中。 如果異步調用過程getStringTask
已完成,並且GetUrlContentLengthAsync
不必等待最終結果,則掛起然后返回到GetUrlContentLengthAsync
將造成成本浪費。在調用方法中,處理模式會繼續。 在等待結果前,調用方可以開展不依賴於
GetUrlContentLengthAsync
結果的其他工作,否則就需等待片刻。 調用方法等待GetUrlContentLengthAsync
,而GetUrlContentLengthAsync
等待GetStringAsync
。 -
GetStringAsync
完成並生成一個字符串結果。 字符串結果不是通過按你預期的方式調用GetStringAsync
所返回的。 (記住,該方法已返回步驟 3 中的一個任務)。相反,字符串結果存儲在表示getStringTask
方法完成的任務中。 await 運算符從getStringTask
中檢索結果。 賦值語句將檢索到的結果賦給contents
。 -
當
GetUrlContentLengthAsync
具有字符串結果時,該方法可以計算字符串長度。 然后,GetUrlContentLengthAsync
工作也將完成,並且等待事件處理程序可繼續使用。 在此主題結尾處的完整示例中,可確認事件處理程序檢索並打印長度結果的值。 如果你不熟悉異步編程,請花 1 分鍾時間考慮同步行為和異步行為之間的差異。 當其工作完成時(第 5 步)會返回一個同步方法,但當其工作掛起時(第 3 步和第 6 步),異步方法會返回一個任務值。 在異步方法最終完成其工作時,任務會標記為已完成,而結果(如果有)將存儲在任務中。
async\await的運行機理
反編譯后我們可以看到async\await的運作機理主要分為分異步狀態機和等待器,現在我主要來講解着兩部分的運行機制。
1、異步狀態機
(1)異步狀態機 始狀態是-1;
(2)運行第一個【等待器】 期間異步狀態機的狀態是0。
(3)第一個【等待器】完成后異步狀態機狀態恢復-1。
(4)運行等二個【等待器】期間 異步狀態機的狀態是1,后面【等待器】以此類推。
(5)當所有的等待器都完成后,異步狀態機的狀態為-2。
異步狀態機的組成
2、等待器
TaskAwaiter等待器
第一個案例中代碼await httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");生成TaskAwaiter類型等待器。該類型等待器繼承ITaskAwaiter接口
(1)有返回值的等待器,它返回值就是異步方法public Task<TResult> GetStringAsync(string? requestUri)返回值Task<TResult> 中的TResult。
此例子 中httpClient.GetStringAsync() 返回值是Task<string>。
所以等待器 TaskAwaiter的返回值是 string(Task<string>中的string)。
ConfiguredTaskAwaiter等待器
通過第 以下代碼了解 等二種等待器類型
await httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html").ConfigureAwait(false);生成ConfiguredTaskAwaitable<string>.ConfiguredTaskAwaiter類型等待器.
該等待器將被設置成bool m_continueOnCapturedContext屬性被設置成fasle, 該等待器繼承IConfiguredTaskAwaiter接口。
StateMachineBoxAwareAwaiter等待器
await Task.Yield();生成IStateMachineBoxAwareAwaiter類型等待器.,該類型的等待器繼承自IStateMachineBoxAwareAwaiter接口
Task.Yield 簡單來說就是創建時就已經完成的 Task ,或者說執行時間為0的 Task ,或者說是空任務。
我們知道在 await 時會釋放當前線程,等所 await 的 Task 完成時會從線程池中申請新的線程繼續執行 await 之后的代碼,這本來是為了解決異步操作(比如IO操作)霸占線程實際卻用不到線程的問題,而 Task.Yield 卻產生了一個不僅沒有異步操作而且什么也不干的 Task ,不是吃飽了撐着嗎?
真正的圖謀是借助 await 實現線程的切換,讓 await 之后的操作重新排隊從線程池中申請線程繼續執行。
將不太重要的比較耗時的操作放在新的線程(重新排隊從線程池中申請到的線程)中執行。
3、通過IL代碼,查看異步的執行過程,以及最后是如何把任務交給線程池運行
異步代碼大概運行順序:
步驟1、主程序main調用異步f方法GetUrlContentLengthAsync()
步驟2、GetUrlContentLengthAsync();完成狀態機初始化,然后調用狀態機中異步任務方法構建器 中的開始函數:stateMachine.<>t__builder.Start(ref stateMachine);
步驟3、運行Start()函數中的AsyncMethodBuilderCore.Start(ref stateMachine);語句,保存當前線程的ExecutionContext(執行狀態機)和SynchronizationContext(同步狀態機),然后執行stateMachine.MoveNext();
該語句調用狀態機的 MoveNext()方法;步驟4 開始進入狀態機的MoveNext()方法;
步驟4、生成等待器 awaiter = <getContent>5__2.GetAwaiter(); 將狀態機更改成0;然后運行<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
步驟5開始, 就是任務是一步一步被送入線程池運行,然后程序把控制權還給調用程序main。
步驟5、運行AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine, ref m_task);
步驟6、生成狀態機盒子,將狀態機和當前線程執行上下文 封裝入狀態機 代碼如下:
IAsyncStateMachineBox stateMachineBox = GetStateMachineBox(ref stateMachine, ref taskField);
然后運行AwaitUnsafeOnCompleted(ref awaiter, stateMachineBox);
步驟7、執行以下代碼。根據不同的等待器類型,選擇進入askAwaiter.UnsafeOnCompletedInternal()方法中。
//第一種等待器 獲取同步上下文 if (default(TAwaiter) != null && awaiter is ITaskAwaiter) { TaskAwaiter.UnsafeOnCompletedInternal(Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter).m_task, box, true); return; } //第二種等待器 根據初始化設置決定要不要獲取同步上下文 if (default(TAwaiter) != null && awaiter is IConfiguredTaskAwaiter) { 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)(object)awaiter).AwaitUnsafeOnCompleted(box); return; } catch (Exception exception) { System.Threading.Tasks.Task.ThrowAsync(exception, null); return; } }
TaskAwaiter.UnsafeOnCompletedInternal方法聲明如下:
internal static void UnsafeOnCompletedInternal(Task task, IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
步驟8、然后運行以下代碼,進入 task.SetContinuationForAwait()方法中
internal static void UnsafeOnCompletedInternal(Task task, IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext) { if (TplEventSource.Log.IsEnabled() || Task.s_asyncDebuggingEnabled) { task.SetContinuationForAwait(OutputWaitEtwEvents(task, stateMachineBox.MoveNextAction), continueOnCapturedContext, false); } else { task.UnsafeSetContinuationForAwait(stateMachineBox, continueOnCapturedContext); } }
運行OutputWaitEtwEvents(task, stateMachineBox.MoveNextAction,將任務和狀態機的moveNext()封裝成新的 委托。然后task.SetContinuationForAwait()運行該委托。
捕獲當前同步上下文,
步驟9、運行以下代碼, 這時候就用到了步驟7 提到的參數continueOnCapturedContext。由於步驟7傳入是true 所以該方法就獲取了SynchronizationContext同步上下文。
internal void SetContinuationForAwait(Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext) { TaskContinuation taskContinuation = null; if (continueOnCapturedContext) { SynchronizationContext current = SynchronizationContext.Current;//獲取SynchronizationContext同步上下文,控制台程序為null,ui程序該項不為null if (current != null && current.GetType() != typeof(SynchronizationContext)) { //SynchronizationContext不等於null線程執行這一步 taskContinuation = new SynchronizationContextAwaitTaskContinuation(current, continuationAction, flowExecutionContext);//將同步上希望一起封裝入了實例TaskContinuation中 } else { //SynchronizationContext等於null線程執行這一步 TaskScheduler internalCurrent = TaskScheduler.InternalCurrent; if (internalCurrent != null && internalCurrent != TaskScheduler.Default) { taskContinuation = new TaskSchedulerAwaitTaskContinuation(internalCurrent, continuationAction, flowExecutionContext); } } } if (taskContinuation == null && flowExecutionContext) { taskContinuation = new AwaitTaskContinuation(continuationAction, true); } if (taskContinuation != null) { if (!AddTaskContinuation(taskContinuation, false)) { //當線程池中的線程完成任務后該線程 會用taskContinuation.Run()中的SynchronizationContext同步上下文運行異步方法中剩下代碼。//由於控制台沒有顯示的SynchronizationContext(控制台有默認的SynchronizationContext),
// 默認的SynchronizationContext沒有轉移控執行權的功能,所以控制台會繼續使用多線程執行剩下異步方法中的代碼。默認的SynchronizationContext內部有計數器來保證剩余的異步方法只執行一次。 // 但是ui線程有SynchronizationContext同步上下文DispachuerSynchronizationContext,該同步上下文會讓會執行權轉移到回到主線程,讓主線程執行剩下的異步方法中的代碼,如果主線程中調用方法中使用task.wait()或者task<Tresult>.result ,
//那么就會形成死鎖。 taskContinuation.Run(this, false); } } else if (!AddTaskContinuation(continuationAction, false)) { //continueOnCapturedContext=false 執行這一步 AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this); } }
步驟10、跟蹤到這里,終於看到真面目了,UnsafeQueueUserWorkItemInternal()方法向線程池的操作請求隊列添加一個工作項以及其他數據項目,然后方法就會立即返回( 回到最開始的調用位置)。我知道還可以繼續跟着,但我覺得跟蹤到這邊已經夠了。
internal static void UnsafeScheduleAction(Action action, Task task) { AwaitTaskContinuation awaitTaskContinuation = new AwaitTaskContinuation(action, false); TplEventSource log = TplEventSource.Log; if (log.IsEnabled() && task != null) { awaitTaskContinuation.m_continuationId = Task.NewId(); log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, awaitTaskContinuation.m_continuationId); } ThreadPool.UnsafeQueueUserWorkItemInternal(awaitTaskContinuation, true); }
回到最開始的調用位置<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
然后執行return;將控制權還給主程序。
private void MoveNext() { int num = <>1__state; int length; try { TaskAwaiter<string> awaiter; if (num != 0) { <httpClient>5__1 = new HttpClient(); <getContent>5__2 = <httpClient>5__1.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html"); awaiter = <getContent>5__2.GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <GetUrlContentLengthAsync>d__1 stateMachine = this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } }
最終線程池中的某個線程會處理該工作項。
參考: https://www.cnblogs.com/lsxqw2004/p/4922374.html