.NET 11 Runtime Async:同步完成配置歸零,掛起路徑減半
Microsoft DevBlogs(.NET Blog)· 2026-09-15
.NET 11 把 async 方法的執行單位,從編譯器展開的狀態機物件換成執行期直接管理的堆疊續體(continuation),同步完成的路徑配置直接歸零。這項機制稱為 Runtime Async,由 Distinguished Engineer Stephen Toub 發表於 .NET Blog,目前以專案旗標選擇性開啟,官方預告 .NET 12 起會預設開啟。
背景
C# 的 async/await 已經用了超過十年,讓非同步程式碼寫起來跟同步程式碼一樣直覺。但編譯器背後其實是把整個方法展開成一個實作 IAsyncStateMachine 的類別:區域變數搬進欄位,每個 await 點變成跳躍表的一個分支,遇到還沒完成的操作時再把整個物件包進 Task 丟回呼叫端。
問題在於即使操作當下就同步完成——這在真實服務裡是常態,例如快取命中、記憶體內查詢——呼叫端仍要付出建立狀態機物件、裝箱、後續 GC 的成本。方法巢狀愈深、await 層數愈多,這筆固定成本就被重複攤提愈多次。
核心改動:Runtime Async
Runtime Async 讓執行期直接管理續體,不再依賴編譯器生成的狀態機類別。啟用方式是在專案檔加一行旗標,不需要新的 C# 語法,也不必開 LangVersion=preview:
<Features>$(Features);runtime-async=on</Features>官方用兩層呼叫鏈的微觀基準測試,對照傳統狀態機(Classic)與 Runtime Async(Runtime)的差異:
| 情境 | Classic | Runtime Async | 比例 |
|---|---|---|---|
| 同步完成 | 21.221 ns,144 B | 6.151 ns,0 B | 0.29x 時間 |
| 掛起後恢復 | 254.139 ns,248 B | 116.927 ns,168 B | 0.46x 時間、0.68x 配置 |
| 例外堆疊深度 10 層 | 19.469 μs,15.13 KB | 5.885 μs,2.13 KB | 0.30x 時間、0.14x 配置 |
| 例外堆疊深度 30 層 | 51.302 μs,84.2 KB | 10.721 μs,5.71 KB | 0.21x 時間、0.07x 配置 |
連編譯後的機器碼都變小:同一段程式碼在 Runtime Async 下產生的機器碼從 10,752 位元組降到 5,632 位元組,減少 52%。
JIT 去抽象化:邊界檢查與裝箱優化
除了 async 重寫,.NET 11 的 JIT 也拿掉了更多因抽象層留下的邊界檢查與裝箱。例如下面這段用 list pattern 比對字串前綴的程式碼:
private static int Classify(ReadOnlySpan<char> name) =>
name switch
{
[] => 0,
[':'] => 1,
[':', not ':', ..] => 10 + name[0] + name[1],
_ => 3
};.NET 10 產生的組合語言裡還留著呼叫 CORINFO_HELP_RNGCHKFAIL 的邊界檢查失敗處理,.NET 11 直接把它拿掉(dotnet/runtime#121273)。連續存取陣列固定索引的加總也把重複邊界檢查合併成一次,耗時從 2.958 ns 降到 1.828 ns,比例 0.62x(dotnet/runtime#127439);讀取 span 尾端 4 個位元組的輔助函式,機器碼從 73 位元組壓到 28 位元組(dotnet/runtime#127488)。可為 null 的實質型別裝箱路徑也有類似優化,常見情境下配置直接消失,耗時降到 0.45x(dotnet/runtime#122167)。foreach 用到的列舉器(enumerator)過去在某些情境下也得先裝箱成介面型別才能走迭代,.NET 11 讓編譯器把這類列舉器直接配置在堆疊上,消除一次 32 位元組的堆積配置(dotnet/runtime#122946)。泛型虛擬方法(GVM)呼叫過去為了處理型別參數,也常常需要額外的裝箱與間接呼叫,這次同樣拿掉了固定的 24 位元組配置。
影響範圍
Runtime Async 是選擇性功能,要在 .NET 11 專案上手動加旗標才會生效——沒開旗標的專案行為不變,仍走原本的編譯器狀態機。高吞吐、深層巢狀 await 呼叫的服務(例如 API gateway、RPC 中介層)升級後值得評估開啟,尤其同步完成比例高的路徑,GC 壓力下降最直接。
啟用前建議先確認第三方函式庫、AOT 編譯或原始碼產生器是否依賴目前的狀態機物件結構(例如用反射檢查 IAsyncStateMachine),因為底層執行模型換了一套。邊界檢查與裝箱的優化則不需要任何動作,只要用 .NET 11 SDK 重新編譯,符合模式的程式碼會自動拿掉多餘檢查,效益大小取決於程式碼是否大量使用 Span<T>、list pattern、可為 null 的實質型別。
原始來源:Performance Improvements in .NET 11、dotnet/runtime PR #121273