Опубликован: 23.01.2013 | Уровень: для всех | Доступ: платный | ВУЗ: Томский политехнический университет
Лекция 11:

Параллельные коллекции. Низкоуровневая синхронизация

BlockingCollection

Коллекция BlockingCollection, представляет собой потокобезопасную коллекцию, которая осуществляет блокировку и ожидает, пока не появится возможность выполнить действие по добавлению или извлечению элемента. Пример использования коллекции BlockingCollection представлен ниже:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Threading.Tasks;


namespace BlockingCollectionExample
{
     class BankAccount {
        public int Balance {
            get;
            set;
        }
    }
    class Deposit {
        public int Amount {
            get;
            set;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
             // создаем коллекцию BlockingCollection 
            BlockingCollection<Deposit> blockingCollection
                = new BlockingCollection<Deposit>();
            // создаем и запускаем задачу, которая будет генерировать депозиты и помещать их в коллекцию
            Task[] producers = new Task[3];
            for (int i = 0; i < 3; i++) {
                producers[i] = Task.Factory.StartNew(() => {
                    // создаем депозиты
                    for (int j = 0; j < 20; j++) {
                     // создаем перевод
                        Deposit deposit = new Deposit { Amount = 100 }; 
                     // помещаем перевод в коллекцию
                        blockingCollection.Add(deposit);
                    }
                });
            };
            // создаем продолжение, которое будет сигнализировать об окончании "поставки" 
            Task.Factory.ContinueWhenAll(producers, antecedents => {
                // создаем сигнал - "поставка" окончено
                Console.WriteLine("Сигнал об окончании производствки");
                blockingCollection.CompleteAdding();
            });
          // создаем банковский счет
            BankAccount account = new BankAccount();
          // создаем потребителя, который будет обновлять баланс, основанный на депозитах
            Task consumer = Task.Factory.StartNew(() => {
                while (!blockingCollection.IsCompleted) {
                    Deposit deposit;
                    // пытаемся получить следующий элемент коллекции 
                    if (blockingCollection.TryTake(out deposit)) {
                        // обновляем баланс с учетом суммы перевода
                        account.Balance += deposit.Amount;
                    }
                }
                // выводим финальный баланс
                Console.WriteLine("Итоговый баланс: {0}", account.Balance);
            });   
            consumer.Wait();
            Console.ReadLine();
        }
    }
} 
 Результат выполнения программы использующую коллекцию BlockingCollection

увеличить изображение
Рис. 15.7. Результат выполнения программы использующую коллекцию BlockingCollection

При использовании коллекции BlockingCollection следует:

  • Создать экземпляр класса BlockingCollection, указав при необходимости коллекцию, реализующую интерфейс IProducerConsumerCollection и максимальный размер коллекции.
  • Создать поставщика;
  • Создать потребителя.
Создание экземпляра класса BlockingCollection

Первым шагом, как говорилось ранее, является создание экземпляра класса BlockingCollection. Как показано в примере класс BlockingCollection - строго типизирован, и тип соответствует классу Debosit:

// создаем коллекцию BlockingCollection 
BlockingCollection<Deposit> blockingCollection = new BlockingCollection<Deposit>();

Конструктор по умолчанию использует неограниченный тип коллекции, это означает, что нет ограничений на количество "выдающихся" рабочих элементов в коллекции. Поставщики могут добавлять элементы в коллекцию гораздо быстрее, чем потребители использовать их. Для создания коллекции с ограничением используйте конструктор с аргументом int, как продемонстрировано ниже:

BlockingCollection<Deposit> blockingCollection = new BlockingCollection<Deposit>(5);

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

Создание поставщика

Следующий шаг - это создание процедуры выполняющую роль поставщика. В нашем примере, создается три задачи, которые создают двадцать экземпляров класса Deposit и добавляют их в коллекцию с помощью метода Add():

   Task[] producers = new Task[3];
            for (int i = 0; i < 3; i++) {
                producers[i] = Task.Factory.StartNew(() => {
                    // создаем депозиты
                    for (int j = 0; j < 20; j++) {
                     // создаем перевод
                        Deposit deposit = new Deposit { Amount = 100 }; 
                     // помещаем перевод в коллекцию
                        blockingCollection.Add(deposit);
                    }
                });
            };

Метод Add() блокируется, до тех пор, пока коллекция BlockingCollection не примет новый элемент данных, т.е если другая задача попытается добавить или взять элемент из коллекции, вызов метода Add() будет заблокирован, пока другая операция, над коллекцией, не будет завершена, это связанно с тем что все операции в BlockingCollection синхронизированы.

Класс BlockingCollection имеет ряд методов для добавления элементов в коллекцию, все эти методы описаны в Табл. 15.7.

Таблица 15.7. Методы для добавления элементов коллекцию BlockingCollection
Имя Описание
Add(T) Добавляет элемент в коллекцию
Add(T, CancellationToken) Добавляет элемент в коллекцию, контролируя токен отмены.
TryAdd(T) Пытается добавить указанный элемент в коллекцию
TryAdd(T, int) Пытается добавить указанный элемент в коллекцию в течение указанного временного периода.
TryAdd(T, TimeSpan) Пытается добавить указанный элемент в коллекцию, в течение указанного временного периода.
TryAdd(T, int, CancellationToken) Пытается добавить указанный элемент в коллекцию в течение указанного временного периода, контролируя токен отмены.
Создание потребителя

Использование модели поставщик/потребитель позволяет создать "асимметрию" между поставщиком и потребителем. Использование данной модели необходимо в том случае, если производство и потребление элемента, занимает разное количество времени, Код ниже демонстрирует реализацию потребителя:

Task consumer = Task.Factory.StartNew(() => {
       while (!blockingCollection.IsCompleted) {
           Deposit deposit;
           // пытаемся получить следующий элемент коллекции 
           if (blockingCollection.TryTake(out deposit)) {
               // обновляем баланс с учетом суммы перевода
               account.Balance += deposit.Amount;
           }
       }
       // выводим финальный баланс
       Console.WriteLine("Итоговый баланс: {0}", account.Balance);
            });

Как видно из примера, задача-потребитель входит в цикл, который выполнится в том случае, если свойство BlockingCollection.IsCompleted возвращает значение false. Свойство IsCompleted возвращает значение true, когда вызывается метод CompleteAdding() и коллекция не содержит элементов, это будет означать, что производство и потребление элементов коллекции, было завершено. Обобщающая информация по данным свойствам и методам представлена в Табл. 15.8.

Таблица 15.8. Методы и свойства для сигнализации окончания производства коллекции BlockingCollection
Имя Описание
CompleteAdding() Сигнализирует что производство закончено
IsAddingComplete Свойство, которое возвращает значение true при вызове метода CompleteAdding()
IsCompleted Свойство, которое возвращает значение true при вызове метода CompleteAdding() и при отсутствии элементов в коллекции.

В то время как в цикле, потребитель вызывает метод TryTake() для получения элемента из коллекции. Этот метод возвращает значение true, если элемент был успешно получен и присваивает значение элемента параметру (out deposit). Можно также было использовать Take(), который блокируется, пока элемент коллекции не будет доступен для потребления. Обобщающая информация по данным свойствам и методам представлена в Табл. 15.9.

Таблица 15.9. Методы и свойства для получения элементов из коллекции BlockingCollection
Имя Описание
Take() Метод извлекает элемент коллекции.
Take(CancellationToken) Метод извлекает элемент коллекции, контролируя указанный токен отмены.
TryTake(out T) Метод удаляет элемент из коллекции
TryTake(out T, int) Метод удаляет элемент из коллекции в течение указанного временного периода.
TryTake(out T, TimeSpan) Метод удаляет элемент из коллекции в течение указанного временного периода.
TryTake(out T, int, CancellationToken) Метод удаляет элемент из коллекции в течение указанного временного периода, контролируя токен отмены.
GetConsumingEnumerable() Метод удаляет элементы из коллекции с помощью цикла foreach, или ForEach(), или запроса PLINQ.
Владимир Каширин
Владимир Каширин

Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010".

При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п.

Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010.

Александр Гаврилов
Александр Гаврилов
Россия
Роман Дмитриев
Роман Дмитриев
Россия, Москва