.NET/C# 異常處理:寫一個空的 try 塊代碼,而把重要代碼寫到 finally 中(Constrained Execution Regions)

  • 2020 年 2 月 10 日
  • 筆記

.NET/C# 異常處理:寫一個空的 try 塊代碼,而把重要代碼寫到 finally 中(Constrained Execution Regions)

發佈於 2018-11-02 21:14 更新於 2018-12-14 01:54

不知你是否見過 try { } finally { } 代碼中,try 塊留空,而只往 finally 中寫代碼的情況呢?這種寫法有其特殊的目的。

本文就來說說這種不一樣的寫法。


空的 try 塊

你可以點開這個鏈接查看 Exception 類,在裏面你可以看到一段異常處理的代碼非常奇怪:

// 代碼已經過簡化。  internal void RestoreExceptionDispatchInfo(ExceptionDispatchInfo exceptionDispatchInfo)  {      // 省略代碼。      try{}      finally      {          // 省略代碼。      }      // 省略代碼。  }

神奇之處就在於,其 try 塊是空的,重要代碼都放在 finally 中。那為什麼會這麼寫呢?

在代碼注釋中的解釋為:

We do this inside a finally clause to ensure ThreadAbort cannot be injected while we have taken the lock. This is to prevent unrelated exception restorations from getting blocked due to TAE.

翻譯過來是:

finally 子句中執行此操作以確保在獲取鎖時無法注入 ThreadAbort。這是為了防止不相關的異常恢復因 TAE 而被阻止。

也就是說,此方法是為了與 Thread.Abort 對抗,防止 Thread.Abort 中斷此處代碼的執行。 Thread.Abort 的執行交給 CLR 管理,finally 的執行也是交給 CLR 管理。CLR 確保 finally 塊執行的時候不會被 Thread.Abort 阻止。

代碼在 .NET Core 和 .NET Framework 中的實現完全一樣:

// This is invoked by ExceptionDispatchInfo.Throw to restore the exception stack trace, corresponding to the original throw of the  // exception, just before the exception is "rethrown".  [SecuritySafeCritical]  internal void RestoreExceptionDispatchInfo(System.Runtime.ExceptionServices.ExceptionDispatchInfo exceptionDispatchInfo)  {      bool fCanProcessException = !(IsImmutableAgileException(this));      // Restore only for non-preallocated exceptions      if (fCanProcessException)      {          // Take a lock to ensure only one thread can restore the details          // at a time against this exception object that could have          // multiple ExceptionDispatchInfo instances associated with it.          //          // We do this inside a finally clause to ensure ThreadAbort cannot          // be injected while we have taken the lock. This is to prevent          // unrelated exception restorations from getting blocked due to TAE.          try{}          finally          {              // When restoring back the fields, we again create a copy and set reference to them              // in the exception object. This will ensure that when this exception is thrown and these              // fields are modified, then EDI's references remain intact.              //              // Since deep copying can throw on OOM, try to get the copies              // outside the lock.              object _stackTraceCopy = (exceptionDispatchInfo.BinaryStackTraceArray == null)?null:DeepCopyStackTrace(exceptionDispatchInfo.BinaryStackTraceArray);              object _dynamicMethodsCopy = (exceptionDispatchInfo.DynamicMethodArray == null)?null:DeepCopyDynamicMethods(exceptionDispatchInfo.DynamicMethodArray);                // Finally, restore the information.              //              // Since EDI can be created at various points during exception dispatch (e.g. at various frames on the stack) for the same exception instance,              // they can have different data to be restored. Thus, to ensure atomicity of restoration from each EDI, perform the restore under a lock.              lock(Exception.s_EDILock)              {                  _watsonBuckets = exceptionDispatchInfo.WatsonBuckets;                  _ipForWatsonBuckets = exceptionDispatchInfo.IPForWatsonBuckets;                  _remoteStackTraceString = exceptionDispatchInfo.RemoteStackTrace;                  SaveStackTracesFromDeepCopy(this, _stackTraceCopy, _dynamicMethodsCopy);              }              _stackTraceString = null;                // Marks the TES state to indicate we have restored foreign exception              // dispatch information.              Exception.PrepareForForeignExceptionRaise();          }      }  }

你可以在 這裡 查看 .NET Framework 版本,在這裡 查看 .NET Core 的版本。

受約束的執行區域(Constrained Execution Regions)

這種現象在微軟官方文檔 可靠性最佳做法 中有介紹。

Doing so instructs the just-in-time compiler to prepare all the code in the finally block before running the try block. This guarantees that the code in the finally block is built and will run in all cases. It is not uncommon in a CER to have an empty try block. Using a CER protects against asynchronous thread aborts and out-of-memory exceptions. See ExecuteCodeWithGuaranteedCleanup for a form of a CER that additionally handles stack overflows for exceedingly deep code.

使用 tryfinally 形成一個受約束的執行區域,使得 finally 中的代碼被可靠地執行。


參考資料

本文會經常更新,請閱讀原文: https://blog.walterlv.com/post/empty-try-block.html ,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。

本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發佈,但務必保留文章署名 呂毅 (包含鏈接: https://blog.walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請 與我聯繫 ([email protected])