2011-08-15 39 views
8

我一直在尋找MSDN,並且無法找到爲什麼線程在finally塊內休眠時無法中斷的原因。我嘗試過放棄而沒有成功。爲什麼線程不會在終於在塊中睡覺時被打斷

有沒有什麼辦法在finally塊內睡眠時喚醒線程?

Thread t = new Thread(ProcessSomething) {IsBackground = false}; 
t.Start(); 
Thread.Sleep(500); 
t.Interrupt(); 
t.Join(); 

private static void ProcessSomething() 
{ 
    try { Console.WriteLine("processing"); } 
    finally 
    { 
     try 
     { 
      Thread.Sleep(Timeout.Infinite); 
     } 
     catch (ThreadInterruptedException ex) 
     { 
      Console.WriteLine(ex.Message); 
     } 
    } 
} 

令人驚訝的MSDN聲稱線程可以在finally塊中止:http://msdn.microsoft.com/en-us/library/aa332364(v=vs.71).aspx 「有機會的話finally塊運行時,線程可以終止,在此情況下,finally塊被中止」

編輯 我找到最佳答案漢斯帕桑特評論,因爲這解釋了爲什麼線程有時並不能中斷/中止在finally塊。那就是當進程關閉時。 謝謝

+3

我不知道這個問題的答案,但我確實是會知道,作爲一般規則,我儘量避免'中斷( )'作爲線程間信號的一種機制。還有更多可預測的API--監視器,{手動|自動} ResetEvent'等 –

+0

當Interrupt/Abort最終不能工作時,我更多地尋找任何解釋/文檔,爲什麼MSDN會說相反。我的例子是更復雜的輪詢類型,我現在正在閱讀它,等待處理的建議。 – Marek

+3

有兩種線程中止。友好的人不會中斷代碼塊。當進程在IsBackground等於true的線程上關閉時,或者當它終止於未處理的異常時,將調用不友好的進程。現在代碼被粗暴地打斷並不重要。 –

回答

8

如果可能的話,應該避免中斷和中斷線程,因爲這會破壞正在運行的程序的狀態。例如,假設你中止了一個持有對資源開放的鎖的線程,這些鎖永遠不會被釋放。

而是考慮使用的信令機制,使線程可以互相合作,並因此處理阻塞和正常疏通,如:

private readonly AutoResetEvent ProcessEvent = new AutoResetEvent(false); 
    private readonly AutoResetEvent WakeEvent = new AutoResetEvent(false); 

    public void Do() 
    { 
     Thread th1 = new Thread(ProcessSomething); 
     th1.IsBackground = false; 
     th1.Start(); 

     ProcessEvent.WaitOne(); 

     Console.WriteLine("Processing started..."); 

     Thread th2 = new Thread(() => WakeEvent.Set()); 
     th2.Start(); 

     th1.Join(); 
     Console.WriteLine("Joined"); 
    } 

    private void ProcessSomething() 
    { 
     try 
     { 
      Console.WriteLine("Processing..."); 
      ProcessEvent.Set(); 
     } 
     finally 
     { 
      WakeEvent.WaitOne(); 
      Console.WriteLine("Woken up..."); 
     } 
    } 

更新

一個相當有趣的低級別問題。儘管Abort()已被記錄,但Interrupt()更是如此。

你的問題的簡短回答是否定的,你不能通過調用AbortInterrupt來喚醒finally塊中的線程。

不能在finally塊中斷或中斷線程是通過設計,只是爲了讓finally塊有機會像預期的那樣運行。如果可以在finally塊中斷和中斷線程,這可能會對清除例程產生意想不到的後果,因此應用程序處於損壞狀態 - 這並不好。

線程中斷的細微差別在於,中斷可能是在線程進入finally塊之前發出的,但它並未處於SleepWaitJoin狀態(即未被阻止)。在這種情況下,如果finally塊中存在阻塞調用,它會立即拋出ThreadInterruptedException並崩潰在finally塊之外。最後,塊保護防止這種情況。

除了在finally塊中進行保護,它還擴展爲嘗試塊和CERs(Constrained Execution Region),它們可以在用戶代碼中進行配置,以防止在執行區域之後引發一系列異常 - 對於關鍵塊非常有用代碼必須完成並延遲中止。

這個例外(無雙關語意思)是所謂的Rude Aborts。這些是由CLR託管環境本身提出的ThreadAbortExceptions。這些可能會導致最終和catch塊被退出,但是而不是 CERs。例如,CLR可能會提出粗魯中止以迴應它認爲花費太長時間完成工作\退出的線程。當試圖卸載AppDomain或在SQL Server CLR中執行代碼時。在您的特定示例中,當您的應用程序關閉並且AppDomain卸載時,CLR將在睡眠線程上發出Rude Abort,因爲會出現AppDomain卸載超時。

終止塊中止和中斷不會發生在用戶代碼中,但兩種情況之間的行爲稍有不同。

中止

當在最後塊上調用線程Abort,調用線程被阻塞。這是documented

調用中止可能會阻止如果被中止線程的代碼,一個受保護的區域,如catch塊,finally塊,或約束的執行區域的線程。

在中止情況下,如果睡眠不是無限的:

  1. 調用線程將發出Abort但直到最後退出塊來阻塞即它在這裏停止,並且不立即進行到Join聲明。
  2. 被調用者線程的狀態設置爲AbortRequested
  3. 被叫方繼續睡覺。
  4. 當被呼叫者醒來時,因爲它的狀態爲AbortRequested它將繼續執行finally塊代碼,然後「蒸發」即退出。
  5. 當中止的線程離開finally塊時:沒有發生異常,執行finally塊後沒有代碼,線程的狀態爲Aborted
  6. 調用線程解除阻塞,繼續到Join語句,並在調用線程退出時立即通過。

所以給出一個無限睡你的榜樣,調用線程將在步驟1中

中斷

在中斷情況下,永遠阻止如果睡眠不是無限的:

沒有那麼好記錄...

  1. 調用線程將發出Interrupt繼續執行。
  2. 調用線程將阻塞Join語句。
  3. 被調用者線程的狀態設置爲在下一次阻塞調用時引發異常,但是至關重要的是,它在finally塊中不會被解除阻塞,即被喚醒。
  4. 被叫方繼續睡覺。
  5. 當被調用者醒來時,它將繼續執行finally塊。
  6. 當中斷的線程離開finally塊時,它會在下一個阻塞調用中拋出一個ThreadInterruptedException(請參閱下面的代碼示例)。
  7. 調用線程「加入」,並繼續作爲被叫線程退出,但是,在第6步未處理ThreadInterruptedException現在已經變平的過程中...

所以再次與無限的睡眠給你的榜樣,調用線程將永遠阻塞,但在步驟2

摘要

因此,儘管AbortInterrupt略有不同的行爲,他們都將導致被調用線程睡眠永遠,並且調用線程阻塞永遠(以你的例子)。

只有粗魯中止可以強制阻塞線程退出finally塊,而這些只能由CLR自籌(你甚至不能使用反射來騙取ThreadAbortException.ExceptionState因爲它使內部CLR調用來獲取AbortReason - 沒有機會在那裏輕易地邪惡......)。

CLR防止用戶代碼導致最終塊過早退出,這有助於防止損壞狀態。

對於略有不同的行爲與Interrupt一個例子:

internal class ThreadInterruptFinally 
{ 
    public static void Do() 
    { 
     Thread t = new Thread(ProcessSomething) { IsBackground = false }; 
     t.Start(); 
     Thread.Sleep(500); 
     t.Interrupt(); 
     t.Join(); 
    } 

    private static void ProcessSomething() 
    { 
     try 
     { 
      Console.WriteLine("processing"); 
     } 
     finally 
     { 
      Thread.Sleep(2 * 1000); 
     } 

     Console.WriteLine("Exited finally..."); 

     Thread.Sleep(0); //<-- ThreadInterruptedException 
    } 
} 
+0

+1很好的答案。 –

4

一個finally塊的整個點是保存一些不會受到中斷或中止影響的東西,無論如何都會正常運行。允許一個finally塊被中止或中斷會大大挫敗重點。可悲的是,正如您所指出的,由於各種競爭條件,finally塊可能會中止或中斷。這就是爲什麼你會看到很多人建議你不要中斷或放棄線程。

相反,使用合作設計。如果某個線程應該被中斷,而不是調用Sleep,請使用定時等待。而不是調用Interrupt表示線程等待的東西。