Герберт Шилдт - C# 4.0: полное руководство
Помимо методов TryAdd() и TryTake(), определяемых параллельно с теми, что указываются в интерфейсе IProducerConsumerCollection<T>, в классе BlockingCollection<T> определяется также ряд собственных методов. Ниже представлены методы, которые будут использоваться в приведенных далее примерах.
public void Add(T item)
public T Take()
Когда метод Add() вызывается для неограниченной коллекции, он добавляет элемент item в коллекцию и затем возвращает управление вызывающей части программы. А когда метод Add() вызывается для ограниченной коллекции, он блокирует доступ к ней, если она заполнена. После того как из коллекции будет удален один элемент или больше, указанный элемент item будет добавлен в коллекцию, и затем произойдет возврат из данного метода. Метод Таке() удаляет элемент из коллекции и возвращает управление вызывающей части программы. (Имеются также варианты обоих методов, принимающие в качестве параметра признак задачи как экземпляр объекта типа CancellationToken.)
Применяя методы Add() и Таке(), можно реализовать простой шаблон "поставщик-потребитель", как показано в приведенном ниже примере программы. В этой программе создается поставщик, формирующий символы от А до Z, а также потребитель, получающий эти символы. При этом создается коллекция типа BlockingCollection<T>, ограниченная 4 элементами.
// Простой пример коллекции типа BlockingCollection.
using System;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;
class BlockingDemo {
static BlockingCollection<char> bc;
// Произвести и поставить символы от А до Z.
static void Producer() {
for(char ch = 'A'; ch <= 'Z'; ch++) {
bc.Add(ch);
Console.WriteLine ("Производится символ " + ch) ;
}
}
// Потребить 26 символов,
static void Consumer() {
for(int i=0; i < 26; i++)
Console .WriteLine ("Потребляется символ " + bc.Take());
}
static void Main() {
// Использовать блокирующую коллекцию, ограниченную 4 элементами,
bc = new BlockingCollection<char>(4);
// Создать задачи поставщика и потребителя.
Task Prod = new Task(Producer);
Task Con = new Task(Consumer);
// Запустить задачи.
Con.Start();
Prod.Start();
// Ожидать завершения обеих задач,
try {
Task.WaitAll(Con, Prod);
} catch(AggregateException exc) {
Console.WriteLine (exc);
} finally {
Con.Dispose();
Prod.Dispose();
bc.Dispose();
}
}
}
Если запустить эту программу на выполнение, то на экране появится смешанный результат, выводимый поставщиком и потребителем. Отчасти это объясняется тем, что коллекция bс ограничена 4 элементами, а это означает, что в нее может быть добавлено только четыре элемента, прежде чем ее придется сократить. В качестве эксперимента попробуйте сделать коллекцию bс неограниченной и понаблюдайте за полученными результатами. В некоторых средах выполнения это приведет к тому, что все элементы коллекции будут сформированы до того, как начнется какое-либо их потребление. Кроме того, попробуйте ограничить коллекцию одним элементом. В этом случае одновременно может быть сформирован лишь один элемент.
Для работы с коллекцией типа BlockingCollection<T> может оказаться полезным и метод CompleteAdding(). Ниже приведена форма его объявления.
public void CompleteAdding()
Вызов этого метода означает, что в коллекцию не будет больше добавлено ни одного элемента. Это приводит к тому, что свойство IsAddingComplete принимает логическое значение true. Если же коллекция пуста, то свойство IsCompleted принимает логическое значение true, и в этом случае вызовы метода Таке() не блокируются. Ниже приведены формы объявления свойств IsAddingComplete и IsCompleted.
public bool IsCompleted { get; }
public bool IsAddingComplete { get; }
Когда коллекция типа BlockingCollection<T> только начинает формироваться, эти свойства содержат логическое значение false. А после вызова метода CompleteAdding() они принимают логическое значение true.
Ниже приведен вариант предыдущего примера программы, измененный с целью продемонстрировать применение метода CompleteAdding(), свойства IsCompleted и метода TryTake().
// Применение методов CompleteAdding(), TryTake()
// и свойства IsCompleted.
using System;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Concurrent;
class BlockingDemo {
static BlockingCollection<char> bc;
// Произвести и поставить символы от А до Z.
static void Producer() {
for (char ch = 'A'; ch <= 'Z'; ch++) {
bc.Add(ch);
Console.WriteLine("Производится символ " + ch);
}
bc.CompleteAdding();
}
// Потреблять символы до тех пор, пока их будет производить поставщик.
static void Consumer() {
char ch;
while(!bc.IsCompleted) {
if(bc.TryTake(out ch))
Console.WriteLine("Потребляется символ " + ch);
}
}
static void Main() {
// Использовать блокирующую коллекцию,
// ограниченную 4 элементами,
bc = new BlockingCollection<char>(4);
// Создать задачи поставщика и потребителя.
Task Prod = new Task(Producer);
Task Con = new Task(Consumer);
// Запустить задачи.
Con.Start();
Prod.Start();
// Ожидать завершения обеих задач,
try {
Task.WaitAll(Con, Prod);
} catch(AggregateException exc) {
Console.WriteLine (exc);
} finally {
Con.Dispose();
Prod.Dispose();
bc.Dispose();
}
}
}
Этот вариант программы дает такой же результат, как и предыдущий. Главное его отличие заключается в том, что теперь метод Producer() может производить и поставлять сколько угодно элементов. С этой целью он просто вызывает метод CompleteAdding(), когда завершает создание элементов. А метод Consumer() лишь "потребляет" произведенные элементы до тех пор, пока свойство IsCompleted не примет логическое значение true.
Несмотря на специфический до некоторой степени характер параллельных коллекций, предназначенных в основном для параллельного программирования, у них, тем не менее, имеется немало общего с обычными, непараллельными коллекциями, описанными в предыдущих разделах. Если же вам приходится работать в среде параллельного программирования, то для организации одновременного доступа к данным из нескольких потоков вам, скорее всего, придется воспользоваться параллельными коллекциями.
Сохранение объектов, определяемых пользователем классов, в коллекции
Ради простоты приведенных выше примеров в коллекции, как правило, сохранялись объекты встроенных типов, в том числе int, string и char. Но ведь в коллекции можно хранить не только объекты встроенных типов. Достоинство коллекций в том и состоит, что в них допускается хранить объекты любого типа, включая объекты определяемых пользователем классов.
Рассмотрим сначала простой пример применения класса необобщенной коллекции ArrayList для хранения информации о товарных запасах. В этом классе инкапсулируется класс Inventory.