2009-12-21 39 views
18

我正嘗試瞭解BlockingCollection的目的,在新的並行堆棧的基於.NET的背景下4.什麼是BlockingCollection(共T)的目的

MSDN文件說:

BlockingCollection用作IProducerConsumerCollection實例的包裝,允許從集合中刪除嘗試阻塞,直到可以刪除數據。同樣,可以創建BlockingCollection來強制實現IProducerConsumerCollection中允許的數據元素數量的上限;然後阻塞該集合的添加嘗試,直到空間可用於存儲添加的項目。

但是,當我查看一些IProducerConsumerCollection的實現時,像ConcurrentQueue一樣,我看到它們提供了一個無鎖的,線程安全的實現。那麼爲什麼需要BlockingCollection提供的鎖定機制? MSDN中的所有示例均通過BlockingCollection包裝器顯示使用這些集合,那麼直接使用這些集合的麻煩是什麼?使用BlockingCollection產生的好處是什麼?

回答

17

無論如何,如果您沒有別的事情要做(甚至直到操作完成才能繼續),阻塞直到可以執行操作是一種方便。

如果您有一個您想讀取數據的非阻塞隊列,並且此刻沒有數據,則必須定期輪詢它或等待某個信號量,直到有數據。如果隊列阻塞,那已經自動完成了。

同樣,如果您嘗試添加到已滿的非阻塞隊列,操作將會失敗,然後您必須找出要執行的操作。阻塞隊列將等待,直到有空間。

如果你有聰明的事情而不是等待(比如檢查另一個隊列中的數據,或者引發QueueTooFullException),那麼你需要非阻塞隊列,但通常情況並非如此。

通常,有一種方法可以指定阻塞隊列的超時時間。

+0

我找不到任何地方 - 「阻塞」是什麼意思,它是「忽略」к「等到」? – Fulproof

+0

「阻塞」意味着「等到操作完成」 – Thilo

7

鎖定的目的是鎖定本身。您可以從集合中讀取多個線程,並且如果沒有可用數據,線程將保持鎖定狀態,直到新數據到達。

此外,通過設置大小限制的功能,您可以讓正在填充集合的生產者線程儘可能多地提供它。當集合達到限制時,線程將會鎖定,直到消費者線程爲數據騰出空間。

通過這種方式,您可以使用該集合來限制數據的吞吐量,而無需自行進行任何檢查。您的線程只是讀取和寫入所有可能的內容,並且集合負責根據需要保持線程正常工作或休眠。

+3

重要的部分是「自己不做任何檢查」。你的生產者和消費者代碼都可以非常簡單,幾乎和你的非並行版本完全一樣,如果沒有任何東西(有用)爲他們做,你仍然可以從睡着的線程中獲益。 – VolkerK

4

這是其中一件事情,一旦你這樣做就容易理解。

對於生產者消費者,讓我們有兩個對象,生產者和消費者。它們在構建時共享一個隊列,這樣它們就可以在它之間寫入。

添加在一個生產消費是相當熟悉,剛剛與CompleteAdding稍有不同:

public class Producer{ 
     private BlockingCollection<string> _queue; 
     public Producer(BlockingCollection<string> queue){_queue = queue;} 

     //a method to do something 
     public MakeStuff() 
     { 
      for(var i=0;i<Int.MaxValue;i++) 
      { 
       _queue.Add("a string!"); 
      } 

      _queue.CompleteAdding(); 
     } 
} 

似乎消費者並不意義 - 直到你意識到的foreach不會停止循環,直到隊列已完成添加。在那之前,如果沒有物品,它會回到睡眠狀態。而且,由於這是生產者和消費者收集的同一個實例,因此您可以讓消費者只在實際需要做的事情上佔用週期,而不必擔心停止,重新啓動等。

public class Consumer() 
{ 
     private BlockingCollection<string> _queue; 
     public Consumer(BlockingCollection<string> queue) 
     { 
      _queue = queue; 
     } 

     public void WriteStuffToFile() 
     { 
      //we'll hold until our queue is done. If we get stuff in the queue, we'll start processing it then 
      foreach(var s in _queue.GetConsumingEnumerable()) 
      { 
      WriteToFile(s); 
      } 
     } 
} 

所以你通過使用集合將它們連接在一起。

var queue = new BlockingCollection<string>(); 
var producer = new Producer(queue); 
var consumer = new Consumer(queue); 

producer.MakeStuff(); 
consumer.WriteStuffToFile(); 
+0

忘了補充,這樣做的原因是我可以把生產者和消費者放在不同的線程中,並且讓主線程去做其他事情。 – Mathieson