2011-09-27 78 views
5

下面是一個例子:如何處理多線程中的競態條件?

if (control.InvokeRequired) 
{ 
    control.BeginInvoke(action, control); 
} 
else 
{ 
    action(control); 
} 

如果條件和之間的BeginInvoke什麼叫控制設置,例如?

具有事件做又如:

var handler = MyEvent; 

if (handler != null) 
{ 
    handler.BeginInvoke(null, EventArgs.Empty, null, null); 
} 

如果MyEvent是第一行,if語句,if語句仍然會執行之間退訂。但是,這是適當的設計?如果取消訂閱也會導致正確調用該事件所需的狀態被破壞?這個解決方案不僅有更多的代碼行(樣板文件),而且它不那麼直觀,並且可能導致客戶端的意外結果。

什麼說你呢?

+0

上面描述的用於事件處理程序的模式存在的原因是保持對處理程序的引用處於活動狀態,因此無法處理。 –

+0

@Mitch Wheat:是的,我並不是說處理程序必須處置,但是如果客戶端退出事件,他或她也可能決定不再需要某種通常只由事件處理程序使用的狀態對象。由於該事件仍然在退訂後出現不幸的時機,因此可能很難追蹤錯誤,因爲預期的結果是處理程序在退訂後不會運行。 –

+0

@Mitch看到我的答案。 –

回答

5

在我看來,如果這是一個問題,你的線程管理和對象生命週期管理都是魯莽的,需要重新檢查。

在第一個示例中,代碼不是對稱的:BeginInvoke不會等待action完成,但直接調用將會;這可能已經是一個bug了。

如果您希望另一個線程可能會處理您正在使用的控件,您別無選擇,只能趕上ObjectDisposedException - 並且可能不會拋出,直到您已經在action之內,並且可能不會在當前線程上感謝BeginInvoke

假設一旦您取消訂閱某個活動,您將不再接收通知,這是不正確的。它甚至不需要多線程發生這種情況 - 只有多個訂戶。如果第一個用戶在處理導致第二個用戶取消訂閱的通知時做了某些操作,那麼當前「正在進行中」的通知仍然會轉到第二個用戶。您可以通過在事件處理程序例程頂部的警衛子句來緩解此問題,但無法阻止它發生。

+0

我會仔細查看允許一個訂閱者導致一個事件,使第二個訂閱者退出同一個事件的代碼 –

+0

+1:消息隊列保證傳送,而不是未送達! – SingleNegationElimination

3

有解決競爭條件的幾個技巧:

  • 裹有互斥整個事情。確保每個線程都必須首先獲得一個鎖,才能在比賽中開始跑步。這樣,只要你獲得鎖定,就知道沒有其他線程正在使用該資源,並且可以安全地完成。
  • 找到一種方法來檢測並從中恢復;這可能非常棘手,但某些類型的應用程序運行良好;處理這個問題的一個典型方法是記錄資源發生變化的次數;如果您完成一項任務並發現版本號與開始時的版本號不同,請閱讀新版本並從頭開始執行任務(或僅僅是失敗)
  • 重新設計應用程序以僅使用原子操作;基本上這意味着使用可以一步完成的操作;這通常涉及「比較和交換」操作,或者將所有事務的數據放入可以原子寫入的單個磁盤塊中。
  • 重新設計應用程序以使用無鎖技術;這個選項只有在滿足硬實時約束比服務每個請求更重要時纔有意義,因爲無鎖設計本質上會丟失數據(通常具有一些低優先級性質)。

哪個選項是「正確」取決於應用程序。每個選項都有性能權衡,可能會使併發性的好處不太吸引人。

-1

如果這種行爲正在蔓延在你的應用程序的多個地方,它可能值得重新設計的API,它看起來像:

if(!control.invokeIfRequired()){ 
    action(action); 
} 

只是同樣的想法爲標準JDK庫ConcurrentHashMap.putIfAbsent(...)。當然,你需要在這個新的control.invokeIfRequired()方法中處理同步。

+0

謝謝,但它已經是擴展方法的一部分:) –

+0

第二個例子非常有趣,因爲'handler'可能被其他線程指定爲null。我們在這裏需要一個鎖對象,所以每個讀寫操作都由同一個鎖保護。我不確定C#。但在Java中,工作代碼可能如下所示:'synchronized(lockObj){if(handler!= null){handler.beginInvoke(...)}}' –

+1

-1此建議雖然有用,但它不適用於這個問題。 –