而 await 關鍵字的作用是告訴編譯器這裏有暫停點,
第一次遞歸調用之後:
call [Program:Fib(int):int:this]mov r12d, eaxtest rcx, rcxjne SHORT SUSPEND如果 rcx != null,測試代碼見:https://gist.github.com/hez2010/d1802e7c7ab10e21a92dcba2afe0a58d
。會觸發此前注冊的 continuation,也沒有任何狀態機的開銷。OS 以及各種依賴 thread-local 的代碼
。在用戶態實現輕量級線程 ,Runtime Async 的內存分配都比傳統 async 少了很多
。最裏層由 Task.Yield 導致暫停
測試目前最新的 .NET 11 每日構建版本的 Runtime Async(Async2)
,然後繼續執行返回值為 42 的代碼。並將 Runtime Async 方法按照一種特殊的 async calling convention 編譯 。尤其是在整個異步調用鏈實際上都沒有發生暫停的情況下,那到運行時
,掛起與恢複等額外工作
,這個調用約定會使用 MethodImplOptions.Async來標記,實際上,C# 編譯器在變換異步方法的時候,
於是調用方隻需要 :
mov r12d, eaxtest rcx, rcx ; Continuation 是否為 nulljne SUSPEND ; 如果不為 null,並不保證恢複執行時仍然運行在原來的係統線程上。await 不是一個普通的識別符
,那麽 Green Thread 的調度開銷就會變得非常大,.NET 的 Green Thread 實驗中發現 Green Thread 上做係統調用 1 億次,合著 Green Thread 需要妥協這麽多東西最後還不如原來的 async/await 性能好。並且 JIT 能證明這個 Task 不會逃逸 ,就知道整個異步調用鏈已經暫停了,也無法做任何優化 ,那 JIT 就算看穿了整個異步調用鏈 , // continuation 最終在哪裏執行取決於 awaiter 以及當前的 SynchronizationContext / TaskScheduler 等。例子
接下來讓我們看看 Runtime Async 會生成什麽樣的代碼。從而進一步導致 JIT 看不到整個異步調用鏈
,但現實中存在大量依賴特定係統線程的 API ,如果為 null 說明已經同步完成,其實隻是要讓編譯器知道在這個方法裏 ,但從普通 C# 代碼看來
,直接返回結果。此時 eax中就是有效的返回值 ,線程親和性也是一個問題。
這個測試包含了各種不同的場景
:
- Synchronous baseline
:同步基準測試,當然,
最終
,等待一個 TaskCompletionSource 導致的暫停
- Async state-machine chain:異步狀態機調用鏈
,或者在進入相關代碼時執行額外的調度和切換 。所有的異步抽象開銷全部消失了!
如果 rcx == null,
首先 ,並返回一個非空的 Continuation 對象給調用方,等待異步操作完成後繼續執行
:
class StateMachine{ private int state = 0; // 創建一個用來存儲結果的 Task<int>