2012-11-16 115 views
16

首先,我將解釋一個簡短的場景;線程安全隊列 - 入隊/出隊

作爲來自某些設備觸發器的信號,Alarm類型的對象被添加到隊列中。每隔一段時間檢查一次隊列,併爲隊列中的每個警報啓動一個方法。

不過,我跑入的問題是,如果警報被添加到隊列中,而它正在走過,它拋出一個錯誤的說,而你使用它的隊列已經改變。這裏有一些代碼來顯示我的隊列,只是假設報警正在不斷插入;

public class AlarmQueueManager 
{ 
    public ConcurrentQueue<Alarm> alarmQueue = new ConcurrentQueue<Alarm>(); 
    System.Timers.Timer timer; 

    public AlarmQueueManager() 
    { 
     timer = new System.Timers.Timer(1000); 
     timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed); 
     timer.Enabled = true; 
    } 

    void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 
    { 
     DeQueueAlarm(); 
    } 

    private void DeQueueAlarm() 
    { 
     try 
     { 
      foreach (Alarm alarm in alarmQueue) 
      { 
       SendAlarm(alarm); 
       alarmQueue.TryDequeue(); 
       //having some trouble here with TryDequeue.. 

      } 
     } 
     catch 
     { 
     } 
    } 

所以我的問題是,我怎麼讓這個更多...線程安全?這樣我就不會遇到這些問題。也許有些東西是,將隊列複製到另一個隊列中工作,然後將從原始隊列處理的警報出隊?

編輯:剛剛被告知併發隊列中,將檢查了這一點,現在

+0

您應該首先從隊列中彈出項目,然後使用通用的線程安全隊列實現來發送SendAlarm。如果您無法處理某件物品,請再次入列。 – cdleonard

回答

18
private void DeQueueAlarm() 
{ 
    Alarm alarm; 
    while (alarmQueue.TryDequeue(out alarm)) 
     SendAlarm(alarm); 
} 

或者,你可以使用:

private void DeQueueAlarm() 
{ 
    foreach (Alarm alarm in alarmQueue) 
     SendAlarm(alarm); 
} 

每MSDN文章上ConcurrentQueue<T>.GetEnumerator

枚舉表示時刻即時快照的隊列內容。在調用GetEnumerator之後,它不反映對該集合的任何更新。枚舉器可以安全地同時讀取和寫入隊列。

因此,當多個線程同時調用您的DeQueueAlarm方法時,會出現兩種方法之間的差異。使用TryQueue的方法,你保證隊列中的每個Alarm只會被處理一次;然而,哪個線程選擇哪個警報是非確定性地確定的。 foreach方法確保每個賽車線程將處理隊列中的所有警報(截至它開始迭代它們的時間點),導致多次處理相同的警報。

如果你想處理每個報警恰好一次,並隨後從隊列中刪除,您應該使用第一種方法。

+0

所以僅供參考,TryDequeue推出了一個警報,我們將其填入先前聲明的警報變量,然後使用它? –

+0

正是。另外,當列表爲空時'TryDequeue'返回'false',導致我們跳出while循環。 – Douglas

+0

這很漂亮:)現在要去測試這個。 –

6

的.Net已經有一個線程安全的隊列實現:看看ConcurrentQueue

+0

我在使用「.TryDequeue()」時遇到了一些麻煩。你能幫我一下嗎? :)我會更新我的問題到ConcurrentQueue –

0

一種更好的方式來處理它,因爲每個線程實際上只處理一次單個報警,將被替換此:

 foreach (Alarm alarm in alarmQueue) 
     { 
      SendAlarm(alarm); 
      alarmQueue.TryDequeue(); 
      //having some trouble here with TryDequeue.. 
     } 

與此:

 while (!alarmQueue.IsEmpty) 
     { 
      Alarm alarm; 
      if (!alarmQueue.TryDequeue(out alarm)) continue; 
      SendAlarm(alarm); 
     } 

沒有理由隨時獲得隊列的完整快照,因爲您只需在每個週期開始時真正關心下一個要處理的隊列。