2012-09-27 410 views
2

鑑於:鎖定未處理的異常處理程序是否安全?

  • 採取了鎖,而在終結可能導致死鎖
  • 終結可以拋出異常

是否安全採取了鎖,而未處理的異常處理程序內還是可以的下面的代碼會導致死鎖?

static void Main(string[] args) 
{ 
    AppDomain.CurrentDomain.UnhandledException += 
     new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); 
    //do other stuff 
} 

private static object loggingLock = new object(); 

static void CurrentDomain_UnhandledException(
    object sender, 
    UnhandledExceptionEventArgs e) 
{ 
    lock (loggingLock) 
    { 
     //log the exception 
    } 
} 
+1

不是一個直接的答案,但也許你可以嘗試將'loggingLock'對象移動到你的日誌類(工作實際完成的地方)。 –

+1

真的是這樣,但我試圖保持代碼儘可能簡單,以便發佈在SO – Yaur

+0

難以置信......這是有道理的。我會發佈一個答案,但我不知道(這就是爲什麼我要看這個開始) –

回答

3

鑑於:

  • 採取了鎖,而在終結可能導致死鎖
  • 終結可以拋出異常

編輯原來,在拋出的異常終結者根據定義是致命的:

doc:如果Finalize或重寫Finalize引發異常,並且運行時未由覆蓋默認策略的應用程序託管,則運行時會終止進程,並且不會執行活動的try-finally塊或終結器。如果終結器無法釋放或銷燬資源,此行爲可確保流程完整性。

c# finalizer throwing exception?

注意見:即使一個異常可能源於一個函數中,這並不意味着它會在該功能的情況下進行處理。事實上,堆棧會被展開:

doc:一個例外是未處理只有當線程整個堆棧已經解開沒有找到適用的異常處理程序,所以首先可以將事件引發的在線程發起的應用程序域中。


我不明白爲什麼它不會是安全的鎖。 (通常的注意事項將適用:不要在按住鎖的同時進行阻止操作...)。

但是,您可能要三思而後重新進入和無限遞歸這裏:

  • 你會如何錯誤迴應,同時記錄?根據定義,鎖定將被獲取,因爲線程已經持有它。但是日誌代碼是否可重入? I.e .:會調用另一個日誌正在進行(失敗/失敗)操作的操作混亂狀態?記錄甚至可能嗎?如果重入不允許(或需要特殊的操作(例如,在其他地方記錄),即使獲取了鎖,也需要明確的'inLoggingOperation'標誌,因爲單線程重入是由鎖定

  • 小點:如果你的日誌記錄不完全例外證明你可能會遇到麻煩的時候就已經在CurrentDomain.UnhandledException(AFAICT the docs do not describe當異常事件處理程序引發會發生什麼)

+0

我已經通過鏈接文檔找到了關於特定情況的一些更多相關信息:請參閱鏈接文檔中的編輯 – sehe

+0

「公共語言運行庫在執行UnhandledException事件的事件處理程序時暫停線程中止。是讓我擔心的部分。 – Yaur

+0

@Yaur你能否具體解釋一下你有什麼擔心? – sehe

0

好,我做了一些狩獵和我發現這個從MSDN:

形式的鎖語句

lock (x) ... 

其中x是引用類型的表達,是恰恰相當於

System.Threading.Monitor.Enter(x); 
try { 
    ... 
} 
finally { 
    System.Threading.Monitor.Exit(x); 
} 

除了x是隻評估一次。

雖然保持互斥鎖,但在同一執行線程中執行的代碼也可以獲取並釋放該鎖。但是,在釋放鎖之前,阻止其他線程中執行的代碼獲取鎖。

8.12 The lock statement

所以,如果異常是從,因爲finally語句的鎖內拋出的鎖將被釋放。

有了這些信息,我會95%確定你不會因試圖從你的CurrentDomain_UnhandledException方法中鎖定而導致死鎖。如果有人不知道我會喜歡從他們這裏(並且引用也會很好)。

+0

你是絕對正確的,lock' ***的語義總是***保證正確釋放(除非進程消失)。但是,這不是我從OP – sehe

+0

中提煉出來的主要問題。我明白的確切問題是「在無人管理的異常處理程序內部鎖定時是否安全......」並且我確信答案是肯定的。爲什麼要處理'lock(...)'擴展到的內容。 –

+0

嗯。不知道我是否同意。 「爲什麼它是安全的」與UnhandledException處理程序可以合法調用(以及)有關。我想你可能會想到相反的情況:「爲什麼拋出會拋出周圍'lock()'子句範圍的異常是安全的?」。這在你的答案中當然可以正確解釋。 – sehe

0

後人...一些測試代碼:

class Program 
{ 
    static AutoResetEvent e1 = new AutoResetEvent(false); 
    static AutoResetEvent e2 = new AutoResetEvent(false); 
    private static object lockObject = new object(); 

    private static void threadProc() 
    { 
     lock (lockObject) 
     { 
      e1.Set(); 
      e2.WaitOne(); 
      Console.WriteLine("got event"); 
     } 
    } 

    private static int finalized = 0; 

    public class finalizerTest 
    { 

     ~finalizerTest() 
     { 
      try 
      { 
       throw new NullReferenceException(); 
      } 
      finally 
      { 
       Interlocked.Increment(ref finalized); 
      } 
     } 
    } 

    static void Main(string[] args) 
    { 
     ThreadPool.QueueUserWorkItem((a) => threadProc()); 
     e1.WaitOne(); 
     AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); 
     { 
      finalizerTest f = new finalizerTest(); 
     } 

     //uncommenting this will cause logging to happen as expected 
     /* 
     while (finalized == 0) 
     { 
      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
     } 
     */ 

    } 

    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 
    { 
     Console.WriteLine("in handler -- pre lock"); 
     e2.Set(); 

     lock (lockObject) 
     { 
      Console.WriteLine("in handler"); 
     } 
    } 
} 

會發生什麼情況是,如果finalizerTest敲定,因爲應用程序離開主輸出上寫着:

in handler -- pre lock 

但如果它是因爲GC.Collect的/ WaitForPending的finialized終結記載:

in handler -- pre lock 
got event 
in handler 

這意味着,在應用程序關閉時從終結器拋出的異常的特定情況下,您可能無法獲得鎖定,但在這種情況下,應用程序最終化隊列已經陷入嚴重困境,鎖定並沒有讓它變得更糟。

在所有其他測試中,我可以認爲所有事情都按預期發生,threadProc被喚醒併發生日誌記錄。