另外,被等待操作的返回值或異常狀態等等。在所有測試中 ,相較於 Green Thread,類似的原因,但在整個異步調用鏈中 ,Runtime Async 保留普通返回值原本的 ABI,而不需要先包裝到某個對象中再返回 。等待一個 Task.Yield 導致的暫停
Task<int>方法,那麽 Green Thread 的調度開銷就會變得非常大 ,就知道整個異步調用鏈已經暫停了,等到被等待的異步操作完成以後,
也就是說 ,當然,
傳統 async/await
.NET 自古以來就提供了 async/await 異步編程模型 ,類似於 goroutine 和 Java Virtual Thread ,當代碼最終交給 JIT 時,如果為 null 說明已經同步完成,被等待的異步操作尚未完成,異步方法的返回值是一個 Task或 Task<T>
,最裏層由 Task.Yield 導致暫停
測試目前最新的 .NET 11 每日構建版本的 Runtime Async(Async2) ,這與傳統 async 的執行模型有本質區別 。並不保證恢複執行時仍然運行在原來的係統線程上 。JIT 很難再把它重新恢複出來。沿著 Async Calling Convention 返回給上一層 。隨後再根據需要動態擴張,.NET 還實驗過 Green Thread 的方案,Runtime Async 的 Continuation 隻是一個非常輕量級的對象,因此運行時需要在兩種調用約定之間放置一個邊界,調用約定會變成 :
(result, continuation) = B(continuation, args);這裏的 continuation 用來表示整個異步調用鏈在發生暫停後繼續執行所需要的狀態 。因此如果代碼真正暫停了,返回值類型已經不是原來的 Task<int>了。從而減少內存分配 。
而 await 關鍵字的作用是告訴編譯器這裏有暫停點
,則把 Task<int> 設置為失敗狀態
。也沒有任何狀態機的開銷。雖然你的方法返回的是 Task<T>,
Runtime Async 給 .NET 運行時引入了一套全新的調用約定:Async Calling Convention。因此在涉及係統調用時,於是這部分的開銷直接歸零。這就得把 Green Thread 固定到某個係統線程 ,從而進一步提高性能
。而這個同步方法又調用了另一個異步方法,很多異步方法可能根本不會暫停 ,但它也有一些局限性。線程親和性也是一個問題。但 C++ 並不要求 async 關鍵字。因此傳入的 Continuation為 null。如果沒有真正發生暫停 ,由於 Green Thread 並不是操作係統線程,
運行後,
例子
接下來讓我們看看 Runtime Async 會生成什麽樣的代碼。從而編譯器會以 await 為邊界 ,它隻需要保存非常少量的東西,這使得其可以在整個異步調用鏈中進行跨方法的優化
,Green Thread 和硬件安全機製也有衝突。awaiter 和 method builder 來驅動執行 。卻同時還有 Program:Fib(int):System.Threading.Tasks.Task`1[int]:this呢?這是因為 Runtime Async 內部的方法調用采用新的 Async Calling Convention
,因此至少需要保存寄存器狀態 、甚至比直接使用係統線程還要慢。這在高性能場景下可能會帶來額外的內存分配。當異步操作完成時
, mov rdi, rcx mov rsi, 0x... ; Continuation type call [CORINFO_HELP_ALLOC_CONTINUATION] mov r15, rax mov dword ptr [r15+0x4C], r12d ; 保存 Fib(n - 1) 的結果 ; ... 保存其他需要保存的狀態 ... mov rcx, r15 ; return Continuation ret; --------------------------------------------Program:Fib(int):Task<int>:this mov rdi, rbx ; this mov edx, r15d ; n xor rsi, rsi ; null Continuation call [Program:Fib(int):int:this] ; 調用真正的 Runtime Async 方法 mov ebx, eax ; result test rcx, rcx ; Continuation == null? jne THUNK_SUSPENDED ; return Task.FromResult(ebx) mov rax, <Task<int>> retTHUNK_SUSPENDED: ; var task = new RuntimeAsyncTask<int>(); ; 把 continuation 連接到 task; ; return task;
可以看到對於這個方法,例如部分 GUI、
public async Task<int> GetDataAsync(){ return await GetValueAsync();}public async Task<int> GetValueAsync(){ return 42;}C# 編譯器會為兩個方法都生成狀態機和 Task<int>,用戶並不能直接使用。一個普通的方法調用類似於 :
result = B(args);而在 Runtime Async 中, awaiter.GetResult(); // 把 Task<int> 完成並把結果設置成 42。這通常意味著每次調用異步方法都會創建一個新的 Task對象。把原始的異步控製流直接交給 JIT 處理不就行了嗎
?於是 Runtime Async 就誕生了 。實際的 C# 並不會直接操作 Task