Томский политехнический университет
Опубликован: 23.01.2013 | Доступ: свободный | Студентов: 1158 / 192 | Длительность: 12:09:00
Лекция 11:

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

Создание приложений с использованием параллельных коллекций

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

BlockingCollection

  1. Создадим консольное приложение и назовем его, к примеру, "BlockingCollectionApplication"
  2. В новом проекте, подключим директивы на использование потоков и параллельных коллекций:
    using System.Threading.Tasks;
    using System.Threading;
    using System.Collections.Concurrent;
  3. Объявляем блокирующую коллекцию символов BlockingCollection<char>:
    class Program
        {
          
                static BlockingCollection<char> bc;
    ….
         }
  4. Создадим статичный метод Produce(), который "производит" и "поставляет" символы от А до Я:
    static void Producer()
       {
           for (char ch = 'A'; ch <= 'Я'; ch++)
           {
               bc.Add(ch);
               Console.WriteLine("Производится символ " + ch);
           }
       }
  5. Создадим статичный метод Consumer(), который будет "потреблять" символы метода Produce():
    static void Consumer()
       {
           for (int i = 0; i < 34; i++)
           {
               Console.WriteLine("Потребляется символ "+bc.Take());
           }
       }

    Примечание. Метод Take удаляет элемент из коллекции.

  6. В методе Main() используем блокирующую коллекцию, ограниченную 4 элементами:
    bc = new BlockingCollection<char>(4)
  7. Создаем задачи "Поставщика" и "Потребителя":
    Task Prod = new Task(Producer)
    Task Con = new Task(Consumer)
  8. Запускаем задачи:
    Con.Start()
    Prod.Start()
  9. Ожидаем завершение задач "Поставщик" и "Потребитель" с помощью метода 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();
        }
  10. Запустим программу. На экране появиться смешанный результат, которые выводятся методами Produce и Consumer:

    Смешанный результат объясняется тем, что коллекция ограничена 4-мя элементами, это означает, что в неё может быть добавлено только 4 элемента, прежде чем её придется сократить.

    К примеру, если убрать ограничение в четыре символа из коллекции:

    bc = new BlockingCollection<char>();

    То получится следующий результат:

  11. Модифицируем метод Producer, добавив в него метод CompleteAdding().:
    static void Producer()
        {
            for (char ch = 'А'; ch <= 'Я'; ch++)
            {
                bc.Add(ch);
                Console.WriteLine("Производится символ " + ch);
            }
        bc.CompleteAdding();
        }

    Вызов метода CompleteAdding() означает, что в коллекцию не будет добавлено ни одного элемента, это приводит к тому, что свойство IsAddingComplete принимает логическое значение true. Если коллекция пуста, то свойство IsCompleted, принимает логическое значение true, и в этом случае вызовы метода Take() не блокируется.

  12. Модифицируем метод Consumer, добавив в него метод TryTake.:
    static void Consumer()
        {
            char ch;
                while(!bc.IsCompleted)
                {
             if(bc.TryTake(out ch))
                Console.WriteLine("Потребляется символ "+bc.Take());
                }       
        }

    Теперь данный метод будет "потреблять" символы до тех пор, пока их будет производить метод-поставщик Consumer().

    Примечание. Метод TryTake() возвращает логическое значение true, если элемент коллекции был удален.

  13. Запустим программу. В окне запущенной программы отобразиться следующий результат:

    Данный вариант программы дает такой же результат, как и предыдущий, только с одним отличием: метод 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();
            }
        }
    
    }
Владимир Каширин
Владимир Каширин

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

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

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