Какова цель BlockingCollection (Of T) - PullRequest
19 голосов
/ 21 декабря 2009

Я пытаюсь понять назначение BlockingCollection в контексте новых параллельных стеков в .NET 4.

Документация MSDN гласит:

BlockingCollection используется в качестве оболочки для экземпляра IProducerConsumerCollection, позволяя блокировать попытки удаления из коллекции до тех пор, пока данные не будут доступны для удаления. Аналогично, BlockingCollection может быть создан для обеспечения верхней границы числа элементов данных, разрешенных в IProducerConsumerCollection; Попытки добавления в коллекцию могут затем блокироваться, пока не освободится место для хранения добавленных элементов.

Однако, когда я смотрю на реализацию некоторых IProducerConsumerCollection, таких как ConcurrentQueue, я вижу, что они предоставляют реализации без блокировок, поточно-ориентированные. Так зачем нужен механизм блокировки, который обеспечивает BlockingCollection? Все примеры в MSDN показывают использование этих коллекций через оболочку BlockingCollection. Какие проблемы возникают при использовании этих коллекций напрямую? Какие преимущества дает использование BlockingCollection?

Ответы [ 4 ]

18 голосов
/ 21 декабря 2009

Блокировка до тех пор, пока операция не может быть выполнена, удобна, если вам все равно больше нечего делать (или, скорее: невозможно продолжить, пока операция не будет выполнена).

Если у вас есть неблокирующая очередь, из которой вы хотите прочитать данные, и в данный момент нет данных, вам придется периодически опрашивать ее или ждать на каком-то семафоре, пока не появятся данные. Если очередь блокируется, это уже выполняется автоматически.

Точно так же, если вы попытаетесь добавить в неблокирующую очередь, которая заполнена, операция просто завершится неудачей, и вам придется выяснить, что делать. Очередь блокировки просто подождет, пока не освободится место.

Если у вас есть что-то умное вместо ожидания (например, проверка другой очереди данных или вызов QueueTooFullException), тогда вам нужна неблокирующая очередь, но часто это не так.

Часто есть способ указать время ожидания для очередей блокировки.

7 голосов
/ 21 декабря 2009

Целью блокировки является сама блокировка. Вы можете прочитать несколько потоков из коллекции, и если нет доступных данных, поток будет оставаться заблокированным до поступления новых данных.

Кроме того, с возможностью установить ограничение размера, вы можете позволить потоку производителя, который заполняет коллекцию, просто подавать в нее столько, сколько он может. Когда сбор достигает предела, поток просто блокируется, пока потоки-потребители не освободят место для данных.

Таким образом, вы можете использовать сбор для регулирования пропускной способности данных, не проверяя себя. Ваши темы просто читают и пишут все, что могут, а коллекция заботится о том, чтобы потоки работали или спали по мере необходимости.

4 голосов
/ 08 ноября 2013

Это одна из тех вещей, которую намного легче понять, как только вы это сделаете.

Для потребителя-производителя давайте два объекта: Производитель и Потребитель. Они оба имеют общую очередь, которую им дают при создании, поэтому они могут писать между ними.

Добавление в продюсере-потребителя довольно знакомо, только с 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 голосов
/ 20 января 2015

Альтернативно, AsyncEx предоставляет AsyncCollection, которая является асинхронной версией BlockingCollection. Смотри https://github.com/StephenCleary/AsyncEx/wiki/AsyncCollection

...