2010-09-09 40 views
4

假設我正在處理來自多個線程的大量輸入數據。當滿足某些標準時,我可能希望將這些數據作爲特定操作的觸發器。但是,這個行爲並不是可重入的;所以連續兩次觸發同一觸發器應該只導致一次運行。AutoResetEvent類型是原子開關的適當選擇嗎?

使用簡單的bool標誌來指示操作當前是否正在運行不是一個可靠的解決方案,因爲競爭條件可能會發生,並且兩個(或更多)線程仍可能最終同時運行同一操作。當然,同步對象上的lock語句中的bool將起作用;但我通常寧願儘可能避免鎖定*。

目前我在這些情況下做的是使用AutoResetEvent作爲原子切換的一種形式。該代碼通常看起來是這樣的:

if (conditionMet && readyForAction.WaitOne(0)) 
{ 
    try 
    { 
     doAction(); 
    } 
    finally 
    { 
     readyForAction.Set(); 
    } 
} 

這工作,但是這讓我感到這可能是一個低於理想的使用情況爲AutoResetEvent類。你會說這是解決這個問題的適當方法,還是使用其他機制會更有意義?


更新:作爲Jon pointed out,使用Monitor.TryEnter作爲鎖定策略(而不是簡單lock,我相信這是大致相當於Monitor.Enter),真的是相當簡單:

if (conditionMet && Monitor.TryEnter(lockObject)) 
{ 
    try 
    { 
     doAction(); 
    } 
    finally 
    { 
     Monitor.Exit(lockObject); 
    } 
} 

那說,我強烈傾向於使用InterlockedHenk's idea。這看起來很簡單。

+0

.NET的哪個版本? .NET 4有許多新的並行功能可能會有所幫助。例如http://blogs.msdn.com/b/csharpfaq/archive/2010/08/12/blocking-collection-and-the-producer-consumer-problem.aspx – 2010-09-09 07:15:59

回答

3

一個簡單的(和快速的)System.Threading.Interlocked.CompareExchange()會做。

//untested 
int flag = 0; 

if (conditionMet && Interlocked.CompareExchange(ref flag, 1, 0) == 0) 
{ 
    try 
    { 
     doAction(); 
    } 
    finally 
    { 
     flag = 0; // no need for locking 
    } 
} 
+0

啊,所以你說:如果它返回1 ,那麼不要輸入代碼?然後在'finally'中調用'Decrement'? – 2010-09-09 06:43:10

+0

@丹,是的,但你將需要CompareExchange,而不是增量。稍後再編輯。 – 2010-09-09 06:44:51

+0

@亨克:對,對...似乎只是「交換」可以工作,實際上,不是嗎? – 2010-09-09 06:46:01

2

「我通常寧願儘可能避免鎖定」 - 爲什麼?它看起來像這裏做的事情非常相似,但使用Windows同步原語而不是CLR。你正在嘗試同步,這樣一次只有一個線程可以運行一段特定的代碼......這聽起來像是對我的鎖定。唯一的區別是,如果鎖已被佔用,則根本不想執行代碼。

你應該能夠使用Monitor.TryEnter達到相同的效果,我個人認爲這有點簡單易懂。 (然後我再次用Java監視器「長大」,所以它更接近我的背景。)

+0

絕對好的一點......我對「鎖定」的厭惡並不是真正的「避免像瘟疫」那樣的態度,更多的是「用簡單的東西去吧可能。」例如,使用'lock'或'Monitor.TryEnter'需要一個獨立的對象用於這個唯一的目的。在這種情況下,這也有點麻煩:我不得不做一些類似'bool切換;鎖(m_switchLock){if(!m_switch){m_switch = true;切換=真; }} if(switched){...} m_switch = false;''授予,'lock',而不是'Monitor.TryEnter',正如你所說的肯定會更簡單。 – 2010-09-09 07:05:54