當前手上處理的系統,整體的程式架構概念可以簡化成:
HW/FW ←→ CLI ←→ GUI
HW/FW 的程式,大多是透由 C/C++ 撰寫,這部分有專業的人員處理開發。
此系統中的 CLI 與 GUI 這兩部分的程式,則是透由 .NET 開發的。也因此隨著 .NET 的跨平台發展,便得以讓這兩部分的程式,皆可以很有序地從 Windows 轉移至 Linux 當中運作。
而在 CLI ←→ GUI 的資訊交換處理,則有大一部份仰賴著使用 .NET 當中的 Named Pipe。
當把系統中要依賴那尾大不掉的 .NET Framework 的程式,一步步地搬遷到 .NET 6 並逐步的穩定運作一段時日後,隨著 .NET 6 也已走入歷史。
考量了 .NET 的發行步調 後,在現行系統的程式要針對 .NET 下一個版本進行遷移選擇時,最終則是選擇跳過 .NET 8 切入到 .NET 10。

當把系統中的各專案的 TFM 切換到 .NET 10 之後,執行時馬上就拋出了如下的錯誤:
Fail to connect the pipe : The operation has timed out.
發生了什麼事?
這問題透過現代化的 AI 指引,雖然很快地就被精準的指向是在 NamedPipeClientStream.Connect(timeout)的使用發生了預期外的狀況。
檢視了這段底層 Pipe 運作的程式之後,除了發現本來自己系統上所設計的運作機制不太合理之處,也看到了 .NET Framework /.NET Core 時代,在 Runtime 的設計上本身就有的缺點,直到了 .NET 5 之後才完善實作的部分。
本篇就先來聊聊後者的這部分。
這部分主要來自於是 Timeout 行為的差異,以下列出幾個 .NET 的改變階段來看:
| Runtime | Timeout 行為 |
|---|---|
| .NET Framework 4.x | 不精準 |
| .NET Core 2.x | 開始修正 |
| .NET Core 3.x | 大幅改善 |
| .NET 5 | 接近現代實作 |
| .NET 6 | 已經很準 |
| .NET 8/9/10 | 幾乎相同 |
在這邊就看到了一個分水嶺
在 .NET Framework / .NET Core / .NET 5 時期:
很多 API 的 timeout 設計並非相當精確,也就是說 timeout 的行為還是會有採取比較模糊的作法。
在 .NET 6 +,則對於 timeout 的觀點就趨近一個很明確策略規範:
Timeout 是要 deterministic 的
所以 .NET 技術團隊在 .NET 6+ 整理完 timeout 行為的一致性後,其原則:
timeout = maximum waiting time
而非過去的:
timeout ≈ approximate waiting time
在 .NET Core 2.x/3.x 時有兩種改善策略進行重構:
- 在 API 設計時,皆是採取更精準的剩餘時間計算觀點
remaining = timeout - (current - start)。 - 並大幅減少 Thread.Sleep polling 的設計。
到了 .NET 5+ 更是對 timeout accounting 改進 且 移除模糊的 retry 行為。
逐步地達成下列三點:
- timeout 行為要一致。
像是 Socket、HttpClient、Task、Semaphore、Pipe … 等,這些的底層類別的運作,timeout 都要一致,讓 timeout = max wait time。 - 減少 busy waiting。
舊版 runtime 的 polling 會造成 latency 不穩 與 CPU 浪費 的問題。 - async API 的一致性。
現在很多 API 都支援 ConnectAsync(CancellationToken),timeout 其實會變成CancellationTokenSource(timeout)所以 runtime 需要 更 deterministic。
因此會發現…
NamedPipeClientStream.Connect(timeout)在從 .NET Framework 升級到 .NET 後出了問題,這是一個 Runtime 行為逐步被修正與一致化 的結果。
所以整體的改善時序與影響會為:
| 事件 | 大致時間 | 影響 |
|---|---|---|
| 有關 busy wait 問題 | 2016 | 早期 .NET Core retry 行為錯誤導致不準 timeout |
| 深入改善 TryConnect 邏輯 | 2017+ | 修正 retry / WaitNamedPipe ordering |
| 多個修正 / 重構 | .NET Core 3.x → 5.x | 逐步統一 timeout accounting |
| .NET 6+ | .NET 6 → 10 | 行為趨於精準一致,但非「突變」 |
有興趣的話,可以深入看看官方 GitHub 的 Issues 有的主要相關的討論:
- Issue #16945 NamedPipeClientStream 早期實作確實有 retry / spin wait 行為。
- Issue #22678 討論 retry 與 WaitNamedPipe ordering 的 race conditions,顯示舊邏輯不是理想的實作。
- Issue #65434 顯示以多 client 重現下舊實作仍有邊緣錯誤,後續版本修正一些錯誤處理邏輯。
就用這張圖總結觀點:

I'm a Microsoft MVP - Developer Technologies (From 2015 ~).

I focus on the following topics: Xamarin Technology, Azure, Mobile DevOps, and Microsoft EM+S.
If you want to know more about them, welcome to my website:
https://jamestsai.tw
本部落格文章之圖片相關後製處理皆透過 Techsmith 公司 所贊助其授權使用之 "Snagit" 與 "Snagit Editor" 軟體製作。