Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010". При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п. Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010. |
Параллельные коллекции. Низкоуровневая синхронизация
Создание приложений с использованием параллельных коллекций
В данном практическом задании будет рассмотрено два примера создания консольного приложения с использованием параллельных коллекций BlockingCollection и ConcurrentQueue.
BlockingCollection
- Создадим консольное приложение и назовем его, к примеру, "BlockingCollectionApplication"
- В новом проекте, подключим директивы на использование потоков и параллельных коллекций:
using System.Threading.Tasks; using System.Threading; using System.Collections.Concurrent;
- Объявляем блокирующую коллекцию символов BlockingCollection<char>:
class Program { static BlockingCollection<char> bc; …. }
- Создадим статичный метод Produce(), который "производит" и "поставляет" символы от А до Я:
static void Producer() { for (char ch = 'A'; ch <= 'Я'; ch++) { bc.Add(ch); Console.WriteLine("Производится символ " + ch); } }
- Создадим статичный метод Consumer(), который будет "потреблять" символы метода Produce():
static void Consumer() { for (int i = 0; i < 34; i++) { Console.WriteLine("Потребляется символ "+bc.Take()); } }
Примечание. Метод Take удаляет элемент из коллекции.
- В методе Main() используем блокирующую коллекцию, ограниченную 4 элементами:
bc = new BlockingCollection<char>(4)
- Создаем задачи "Поставщика" и "Потребителя":
Task Prod = new Task(Producer) Task Con = new Task(Consumer)
- Запускаем задачи:
Con.Start() Prod.Start()
- Ожидаем завершение задач "Поставщик" и "Потребитель" с помощью метода WaitAll():
try { Task.WaitAll(Con, Prod); } catch (AggregateException exc) { Console.WriteLine(exc); } finally { Con.Dispose(); Prod.Dispose(); bc.Dispose(); }
В итоге получаем следующее содержание метода Main():
static void Main(string[] args) { 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(); } Console.ReadLine(); }
- Запустим программу. На экране появиться смешанный результат, которые выводятся методами Produce и Consumer:
Смешанный результат объясняется тем, что коллекция ограничена 4-мя элементами, это означает, что в неё может быть добавлено только 4 элемента, прежде чем её придется сократить.
К примеру, если убрать ограничение в четыре символа из коллекции:
bc = new BlockingCollection<char>();
То получится следующий результат:
- Модифицируем метод Producer, добавив в него метод CompleteAdding().:
static void Producer() { for (char ch = 'А'; ch <= 'Я'; ch++) { bc.Add(ch); Console.WriteLine("Производится символ " + ch); } bc.CompleteAdding(); }
Вызов метода CompleteAdding() означает, что в коллекцию не будет добавлено ни одного элемента, это приводит к тому, что свойство IsAddingComplete принимает логическое значение true. Если коллекция пуста, то свойство IsCompleted, принимает логическое значение true, и в этом случае вызовы метода Take() не блокируется.
- Модифицируем метод Consumer, добавив в него метод TryTake.:
static void Consumer() { char ch; while(!bc.IsCompleted) { if(bc.TryTake(out ch)) Console.WriteLine("Потребляется символ "+bc.Take()); } }
Теперь данный метод будет "потреблять" символы до тех пор, пока их будет производить метод-поставщик Consumer().
Примечание. Метод TryTake() возвращает логическое значение true, если элемент коллекции был удален.
- Запустим программу. В окне запущенной программы отобразиться следующий результат:
Данный вариант программы дает такой же результат, как и предыдущий, только с одним отличием: метод Producer() может "производить" и "поставлять" сколько угодно элементов. С этой целью он вызывает метод CompleteAdding() когда завершает создание элементов. Метод Consumer() лишь "потребляет" произведенные элементы до тех пор, пока свойство IsCompleted не примет значение true.
Листинг кода программы:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; using System.Collections.Concurrent; namespace BlockingCollectionApplication { class Program { static BlockingCollection<char> bc; static void Producer() { for (char ch = 'А'; ch <= 'Я'; ch++) { bc.Add(ch); Console.WriteLine("Производится символ " + ch); } bc.CompleteAdding(); } static void Consumer() { char ch; while(!bc.IsCompleted) { if(bc.TryTake(out ch)) Console.WriteLine("Потребляется символ "+bc.Take()); } } static void Main(string[] args) { 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(); } Console.ReadLine(); } } }