當應用程式因未處理例外而崩潰時,未處理例外處理 是最後一道防線。它不僅決定應用程式如何結束,更關鍵地提供了診斷根因的最後機會——透過事件日誌、Watson 儲存桶與堆疊追蹤。本文將從 CLR 內部機制出發,系統性剖析 .NET Framework 2.0+ 中托管例外的未處理處理流程。
托管程式碼拋出例外時,CLR 從擲出點最近的托管框架開始,沿著呼叫堆疊向上搜尋 catch 區塊或符合條件的托管篩選器。找到處理器後,CLR 執行 第二階段(Unwind):從擲出點到處理器之間的所有 finally/fault 區塊。最後在處理器中繼續執行。
能執行托管程式碼的執行緒分兩類,其未處理例外觸發機制截然不同:
| 執行緒類型 | 建立者 | CLR 是否控制基礎框架 | 範例 |
|---|---|---|---|
| CLR 建立的執行緒 | CLR | 是 | System.Threading.Thread、Finalizer 執行緒、ThreadPool 執行緒 |
| 非 CLR 建立的執行緒 | 原生碼(COM、Host API、反向 P/Invoke) | 否 | 進入點執行緒、COM Interop 執行緒、CLR Host API 執行緒 |
catch),隱藏錯誤。<legacyUnhandledExceptionPolicy enabled="1"/> 可恢復舊行為(僅供舊應用程式過渡使用)。最佳實務:避免使用
catch(Exception)這類廣義捕捉,它們在語義上等同於 1.x 的吞噬行為,會掩蓋真實錯誤。應捕捉具體例外類型,並盡可能貼近擲出點處理。
例外若在托管碼未被處理,會以原生 SEH 例外形式繼續向上傳播,由 OS 尋找處理器。兩種結果:
Windows 允許進程註冊全域 UEF 回調(SetUnhandledExceptionFilter),OS 僅追蹤最新註冊的回調。CLR 初始化時會註冊自己的 UEF,但鏈結極易斷裂:
若發現 CLR 未處理例外處理未啟動,極大機率是上述兩種情況之一。
AppDomain.CurrentDomain.UnhandledException += (s, e) => {
var ex = (Exception)e.ExceptionObject;
Log.Fatal("未處理例外: " + ex.ToString());
};
StackOverflowException、OutOfMemoryException),僅顯示簡短字串避免遞迴崩潰。最後將控制權交還給呼叫者(OS 或 CLR)。
CLR 宿主可透過 ICLRErrorReportingManager::GetBucketParametersForCurrentException 取得當前執行緒例外的儲存桶參數,整合自訂錯誤報告管線。
本文基於 .NET Framework 2.0 CLR 實作細節,核心概念(執行緒基礎差異、UEF 鏈結脆弱性、三階段處理流程)在後續版本中保持穩定。理解這些機制,能幫助開發者:
UnhandledException 事件中完整記錄診斷資訊;作者:Gaurav Khanna,Microsoft CLR 團隊軟體開發工程師,負責托管例外處理實作與 CLR 宿主。
問題與建議請寄:clrinout@microsoft.com