Подтвердить что ты не робот

Какова цель BlockingCollection (Of T)

Я пытаюсь понять цель BlockingCollection в контексте новых Parallel Stacks на .NET 4.

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

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

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

4b9b3361

Ответ 1

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

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

Аналогично, если вы попытаетесь добавить в неблокирующую очередь, которая будет заполнена, операция будет просто терпеть неудачу, а затем вы должны выяснить, что делать. Блокирующая очередь будет просто ждать, пока не будет места.

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

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

Ответ 2

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

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

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

Ответ 3

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

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

Добавление в потребителя-производителя довольно знакомо, просто с 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();