2012-09-28 46 views
0

我有一個消費者和一個生產者任務的WinForms應用程序。我的生產者任務定期連接到Web服務並檢索指定數量的字符串,然後需要將其放入某種併發固定大小的FIFO隊列中。然後,我的消費者任務處理這些字符串,然後以SMS消息發送(每個消息一個字符串)。我的生產者任務調用的SOAP函數需要一個參數來指定我想要獲取的字符串數量。該號碼將取決於我的隊列中可用的空間。所以如果我有一個最大隊列大小爲100個字符串,並且隊列中有60個字符串,那麼下一次我的生產者輪詢Web服務時,我需要它來請求40個字符串,因爲那時我可以放入隊列中。使用列表的消費者/生產者模式

下面是我使用的是代表我固定大小的FIFO隊列代碼:

public class FixedSizeQueue<T> 
{ 
    private readonly List<T> queue = new List<T>(); 
    private readonly object syncObj = new object(); 

    public int Size { get; private set; } 

    public FixedSizeQueue(int size) 
    { 
     Size = size; 
    } 

    public void Enqueue(T obj) 
    { 
     lock (syncObj) 
     { 
      queue.Insert(0, obj); 

      if (queue.Count > Size) 
      { 
       queue.RemoveRange(Size, queue.Count - Size); 
      } 
     } 
    } 

    public T[] Dequeue() 
    { 
     lock (syncObj) 
     { 
      var result = queue.ToArray(); 
      queue.Clear(); 
      return result; 
     } 
    } 

    public T Peek() 
    { 
     lock (syncObj) 
     { 
      var result = queue[0]; 
      return result; 
     } 
    } 

    public int GetCount() 
    { 
     lock (syncObj) 
     { 
      return queue.Count; 
     } 
    } 

我的製片任務目前不指定我從Web服務需要串的數量,但它似乎就像它可以像獲取隊列中的當前項目數(q.GetCount())一樣簡單,然後從我的最大隊列大小中減去它。但是,儘管GetCount()使用鎖,但GetCount()退出後不可能,我的使用者任務可能會處理隊列中的10個字符串,這意味着我實際上無法將隊列保持爲100%充分?

此外,我的消費者任務基本上需要「偷看」隊列中的第一個字符串,然後嘗試通過SMS消息發送消息。如果郵件無法發送,我需要將該字符串保留在隊列中的原始位置。我首先想到的是,「偷看」隊列中的第一個字符串,嘗試通過SMS消息發送它,然後如果發送成功,則將其從隊列中刪除。這樣,如果發送失敗,字符串仍然在隊列中的原始位置。這聽起來合理嗎?

+1

您可能要考慮使用'System.Collection.Concurrent.ConcurrentQueue '而不是'List ',因爲那樣會爲您處理很多鎖定。 –

回答

1

這是一個廣泛的問題,所以確實沒有明確的答案,但這裏是我的想法。

儘管GetCount()使用鎖,但GetCount()退出後,我的消費者任務可能會處理隊列中的10個字符串,這意味着我永遠無法實現保持隊列100%滿?

是的,除非您在查詢Web服務的整個過程中鎖定syncObj。但生產者/消費者的觀點是允許消費者在生產者獲取更多物品的同時處理物品。關於這點真的沒什麼可做的。在點,隊列將不會100%滿。如果它始終是100%滿的,那麼這意味着消費者根本沒有做任何事情。

這樣,如果發送失敗,字符串仍然在隊列中的原始位置。這聽起來合理嗎?

也許,但你有這種編碼的方式,一個Dequeue()操作返回隊列的整個狀態並清除它。給出這個接口的唯一選擇是對失敗的項目進行重新排隊以便稍後處理,這是一種非常合理的技術。

我也會考慮添加一種方式讓消費者自行封鎖,直到有項目需要處理。例如:

public T[] WaitForItemAndDequeue(TimeSpan timeout) 
{ 
    lock (syncObj) { 
     if (queue.Count == 0 && !Monitor.Wait(syncObj, timeout)) { 
      return null; // Timeout expired 
     } 

     return Dequeue(); 
    } 
} 

public T[] WaitForItem() 
{ 
    lock (syncObj) { 
     while (queue.Count != 0) { 
      Monitor.Wait(syncObj); 
     } 

     return Dequeue(); 
    } 
} 

然後你必須改變Enqueue()調用Monitor.Pulse(syncObj)它操縱名單(所以在方法的結束,但lock塊內)之後。

+0

謝謝cdhowie。這些都是很好的建議,我感謝您花時間發表評論。回想起來,我意識到這是一個相當開放的問題。在發佈之前,我應該更好地縮小焦點。 – user685869