2012-01-28 85 views
6

我遇到了C#中的多線程問題。 我使用事件從另一個線程更新表單中的標籤,我當然需要使用Invoke()命令。 這部分也工作正常。 但是,用戶可以關閉表單,如果事件在不幸的時間發送,程序可能會崩潰。因此,我認爲我會重寫窗體的Dispose()方法,在鎖定的代碼中將布爾值設置爲true,並檢查該布爾值並在鎖定的代碼中調用該事件。只有一個鎖定對象的'死鎖'?

但是,每次我關閉窗體程序完全凍結。

這裏是代碼中提到的部分:

private object dispose_lock = new object(); 
private bool _disposed = false; 

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     lock (dispose_lock) 
     { 
      if (_disposed) return; 
      Invoke(handler); // this is where it crashes without using the lock 
     } 
     return; 
    } 

    label.Text = "blah"; 
} 

protected override void Dispose(bool disposing) 
{ 
    eventfullObject.OnUpdate -= update; 
    lock (dispose_lock) // this is where it seems to freeze 
    { 
     _disposed = true; // this is never called 
    } 
    base.Dispose(disposing); 
} 

我希望在這裏任何人有任何的想法有什麼不對這個代碼。 提前謝謝!

+0

可在實際應用中的更新調用導致窗口處置?在這種情況下,後臺線程可能會有一個鎖,並且UI線程可能會在Dispose鎖定在後臺線程所持的相同對象上。 – 2012-01-28 14:39:39

+1

你從哪裏得到變量InvokeRequired,它應該在你想要更新的控件上調用,即:if(label.InvokeRequired){//} – Lloyd 2012-01-28 15:01:44

回答

1

我真的會在這裏簡單。我不會執行棘手的線程安全代碼,而只會捕獲異常,如果失敗則什麼也不做。

假設這是一個ObjectDisposedException

try 
{ 
    this.Invoke(Invoke(handler)); 
} 
catch (ObjectDisposedException) 
{ 
    // Won't do anything here as 
    // the object is not in the good state (diposed when closed) 
    // so we can't invoke. 
} 

更簡單,非常簡單。如果評論指定爲什麼你發現異常,我認爲沒關係。

+1

壞主意IMO ... – CodesInChaos 2012-01-28 14:55:47

+0

@CodeInChaos我不認爲鎖的複雜性,Dispose的重寫等。比簡單地捕捉異常要好。也許你可以解釋**爲什麼**你認爲這是一個壞主意...... – ken2k 2012-01-28 14:58:56

+0

相同的觀點在這裏:http://stackoverflow.com/a/1874785/870604 – ken2k 2012-01-28 15:00:15

6

你沒有考慮到的是,委託傳遞給Invoke在UI線程上被異步調用。調用Invoke將消息發佈到表單消息隊列中,並在一段時間之後拾取。

會發生什麼事是不是:

UI Thread     Background Thread 
          Call update() 
          take lock 
          Call Invoke() 
Call update()    
          release lock 
Call Dispose() 
take lock 
release lock 

但是相反:

UI Thread     Background Thread 
          Call update() 
           take lock 
           Call Invoke() 
           block until UI Thread processes the message 
Process messages 
... 
Dispose() 
    wait for lock ****** Deadlock! ***** 
... 
Call update()    
          release lock 

正因爲如此,後臺線程可持有被持有鎖,而UI線程試圖運行Dispose

解決方案比您嘗試的要簡單得多。由於Invoke異步發佈,因此不需要鎖定。

private bool _disposed = false; 

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     Invoke(handler); 
     return; 
    } 

    if (_disposed) return; 

    label.Text = "blah"; 
} 

protected override void Dispose(bool disposing) 
{ 
    eventfullObject.OnUpdate -= update; 
    _disposed = true; // this is never called 
    base.Dispose(disposing); 
} 

_disposed標誌只在UI線程上讀取或寫入,因此不需要鎖定。現在你調用堆棧的樣子:

UI Thread     Background Thread 
          Call update() 
           take lock 
           Call Invoke() 
           block until UI Thread processes the message 
Process messages 
... 
Dispose() 
    _disposed = true; 
... 

Call update() 
    _disposed is true so do nothing    
+1

Invoke調用總是失敗。在調用Control的精確時間調用Invoke時,代碼仍然會導致發生無法解析的異常 – JaredPar 2012-01-28 15:56:13

+0

Invoke始終是同步的。 – usr 2012-01-28 16:08:48

+0

@usr我想描述的是,Invoke與UI線程是異步的。它與後臺線程同步。我會盡量讓我的答案更清楚 – shf301 2012-01-28 17:14:01

0

IMO Dispose爲時已晚......

我建議把一些代碼爲FormClosingDispose發生AFAIK之前被調用。

對於這種情況,我通常傾向於爲您的支票使用不同的(原子)模式 - 例如,通過Interlocked類。

private long _runnable = 1; 

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     if (Interlocked.Read (ref _runnable) == 1) Invoke(handler); 
     return; 
    } 

    label.Text = "blah"; 
} 

FormClosing你只需要調用Interlocked.Increment (ref _runnable)

0

只要沒有其他的答案是罪魁禍首,是否有其他代碼終止未發佈的線程?我想你可能會使用普通線程,而不是一個BackgroundWorker,並有可能忘記設置Thread.isBackround爲true

1

一個使用Control.Invoke的危險是,它可以在一個不幸的時間設置在UI線程上你建議。發生這種情況最常見的方式是當你有事件

  1. 後臺線程的順序如下:排隊一個電話回來調用
  2. 前臺線程:配置的控制上,要求調用
  3. 前臺線程背景:在出售的控件上退出回調

在這種情況下,調用將失敗並導致在後臺線程上引發異常。這可能是導致您的應用程序首先崩潰的原因。

儘管這會導致死鎖,但新代碼仍然存在。代碼將在步驟#1中進行鎖定。然後處理在步驟#2發生在用戶界面中,並且它正在等待鎖定,該鎖定在步驟#3完成之後不會被釋放。

來解決這個問題最簡單的方法是接受Invoke是能夠而且將因此失敗的操作需要一個try/catch

private void update(object sender, EventArgs e) 
{ 
    if (InvokeRequired) 
    { 
     EventHandler handler = new EventHandler(update); 
     try 
     { 
      Invoke(handler); 
     } 
     catch (Exception) 
     { 
      // Control disposed while invoking. Nothing to do 
     } 
     return; 
    } 

    label.Text = "blah"; 
} 
1

你爲什麼不只是使用的BeginInvoke,而不是調用 - 這億韓元不會阻止後臺線程。它看起來並不像有就是後臺線程需要等待從你表現出

1

調用Dispatcher.Invoke(在 一個WPF應用程序時,另一種死鎖的情況出現要發生的UI更新任何具體原因)或Control.Invoke(在Windows窗體應用程序中) ,同時擁有一個鎖。如果UI碰巧運行的另一個 方法在等待對同一個鎖,死鎖會發生右 那裏。這通常可以通過調用BeginInvoke而不是Invoke的 來解決。另外,您也可以在調用 調用之前釋放你的鎖,不過如果你的來電者拿出鎖這是不行的。我們 解釋調用而BeginInvoke在富客戶端應用和線程 親和力。

來源:http://www.albahari.com/threading/part2.aspx