Главная » C# » Реализация архитектуры "поставщик/потребитель" в Visual C# (Sharp)

0

Методика "поставщик/потребитель" не реализована в виде типа, но применятся во многих многопоточных приложениях. Идея архитектуры "поставщик/потребитель" заключается в разбиении проблемы на две подпроблемы. С одной стороны нахится поставщик данных, информации и задач. Поставщик инкапсулирует инфоацию в задачу, которую нужно выполнить. Со другой стороны  находится потритель, ответственный за извлечение данных и их обработку.

Скрытая реализация "поставщик/потребитель"

В интерфейсе GUI  Windows в многопоточных приложениях обращаться к компентам пользовательского интерфейса могут только создавшие их потоки. Чтобы обойти эту проблему, применяется метод invoke () библиотеки windows. Forms. Для демонстрации использования этого метода мы создадим приложения GUI, в котом применяется другой поток для периодического увеличения значения счетчика, которое выводится в текстовом поле. Приложение создается такой последователостью шагов:

1. Создайте новое приложение Windows Forms и установите его стартовым прктом (щелкните правой кнопкой по имени проекта и выберите опцию Set As StartUp Project).

2. Поместите элемент управления TextBox на форму Forml.

3. Выберите элемент управления TextBox. Если окно Properties не открыто, то щелкните правой кнопкой мыши по элементу управления и выберите команду Properties.

4.  Измените свойство Name элемента управления TextBox на  txtMessage.

5. Щелкните правой кнопкой мыши по форме и выберите команду View Code.

6. Добавьте следующий код:

public partial class Forml: Form { public Forml() {

InitializeComponent();

}

private int _counter;

private void IncrementCounter() {  txtMessage.Text = "Counter (" + _oounter + ")";

_counter++;

}

delegate void DelegatelncremsntCounter(); private void Periodiclncrement() {

while (1 == 1) {

Invoke(new DelegatelncremsntCounter(IncrementCounter)); Thread.Sleep(1000);

}

}

Thread _thread;

}

7. Переключитесь  обратно  в  представление  формы  и дважды  щелкните  мыщью  по форме. Это откроет код метода Formi_Load ().

8. Добавьте следующий  код в код метода Formi_Load ():

private void Forml_Load(object sender, EventArgs e) {

„thread = new Thread(new ThreadStart(Periodiclncrement));

_thread.start();

Метод Formi_Load () исполняется при загрузке  формы  Formi и  создает  новый  пок, который потом исполняет метод Periodiclncrement (). В реализации метода Periodiclncrement ()     имеется     бесконечный     цикл,      вызывающий      метод Form, invoke (), которому передается делегат. Делегат является методом incrementcounter(), который увеличивает счетчик и  выводит  результат  в  текстом  поле  txtMessage.

С точки зрения пользователя, казалось бы, очевидным вызывать метод incrementcounter () непосредственно из другого потока (_thread). Но реализация метода invoked скрывает реализацию поставщика/потребителя. Поставщиком яяется   метод   invoke (),   добавляющий  делегата,   которого   необходимо   поставить в очередь. А потребителем является класс windows. Forms. Form, который периодички  проверяет очередь метода  invoke ()  и исполняет находящихся  в  ней делегатов.

Вкратце, реализация "поставщик/потребитель" является нечто большим, чем  перача  информации  от  одного  потока  другому.  Этот  процесс  является  эффективным, т. к. поставщик и потребитель разделены, и каждый занимается  собственными  дами. Единственной общей точкой между поставщиком  и потребителем является очередь, или список, которая синхронизируется и содержит информацию, претавляющую  интерес  как для  поставщика, так и для  потребителя.

Реализация общей архитектуры "поставщик/потребитель"

Архитектура, реализуемая windows. Forms, элегантна и самодостаточна. По образцу модели invoke о можно  реализовать  общую  архитектуру  поставщика/потребеля,  как показано  в следующем  коде:

interface IProducerConsumer { ‘ void Invoke(Delegate Sdelegate);

void Invoke(Delegate Sdelegate, Object[] arguments);

}

class ThreadPoolProducerConsumer : IProducerConsumer { class Executor {

public readonly Delegate „delegate;

public readonly Object[] „arguments;

public Executor(Delegate ©delegate, Object[] arguments) {

„delegate ‘= Sdelegate;

„arguments = arguments;

}

private Queue< Executor> „queue = new Queue<Executor>();

private void QueueProcessor(Object obj) { Monitor.Enter(„queue);

while („queue.Count == 0) { Monitor.Wait(„queue, -1) ;

}

Executor exec = „queue.Dequeue(); Monitor.Exit(„queue);

ThreadPool.QueueUserWorkItem(new WaitCallback(QueueProcessor)); exec.„delegate.Dynamiclnvoke(exec.„arguments);

}

public SingleThreaderProducerConsumer() { ThreadPool.QueueUserWorkltem(new WaitCallback(QueueProcessor));

}

public void Invoke(Delegate (Sdelegate, Object[] arguments) { Monitor.Enter(„queue);

„queue.Enqueue(new Executor(@delegate, arguments)); Monitor.Pulse(„queue);

Monitor.Exit(„queue);

}

}

Клас с ThreadPoolProducerConsumer имее т ОДИН общи й мето д Invoke () , которы й используетс я подобн о метод у  invoke()  библиотек и  windows.Forms. Чт о  делае т  об – щи х поставщика/потребител я работоспособными , та к эт о применени е синхрониза – ционног о   класс а  Monitor.

Чтоб ы понять , каки м образо м клас с Monitor работае т в контекст е "постав – щик/потребитель" ,  рассмотри м   реализаци ю   "поставщик/потребитель "   в   целом .   По – то к потребител я (QueueProcessor () ) выполняетс я ПОСТОЯННО, ожида я элемент ы и з очеред и („queue). Дл я проверк и очеред и вызываетс я  мето д  Monitor.Enter () ,  кото – рой , в сущности ,  говорит :  " Я  хоч у  исключительны й  контрол ь  на д  блоко м  кода , которы й   заканчиваетс я   вызово м   метод а   Monitor.Exit о". Дл я   проверк и   очеред и

запускается цикл while. Цикл ожидает до тех  пор,  пока  в  очереди  нет  элемента для обработки. Данный поток мог бы исполняться постоянно, ожидая добавления элементов в очередь, но пока он исполняет цикл, он удерживает блокировку. А это означает,  что  поток поставщика не может добавить  ничего  в очередь.

Таким образом, потребитель  должен  освободить  блокировку,  чтобы  в  очередь можно было добавить элементы, но в то же самое время он должен  проверять  очедь на наличие  в  ней элементов для обработки. Выходом  из этой ситуации являея использование метода Monitor.wait(), который вынуждает поток потребителя освободить блокировку и заявить: "Я временно освобождаю блокировку до тех пор, пока мне не будет дан  сигнал  продолжить  обработку".  Освободив  блокировку,  пок потребителя  переходит  в  режим  сна  и ожидает сигнал для  пробуждения.

Поток поставщика (invoke ()) также входит в защищенный блок, используя метод Monitor. Enter ().  Внутри  защищенного блока кода он добавляет элемент в очередь с помощью метода Enqueue (). Так как в очередь был добавлен элемент, то потооставщик с помощью метода Monitor. Pulse о посылает сигнал, указывающий наличие элементов в очереди. Это заставит поток, который временно освободил блокировку (поток-потребитель), выйти из режима сна. Но поток-потребитель волняется   только   после   вызова   потоком-поставщиком    метода   Monitor.Exito. До тех  пор  поток-потребитель  находится  в режиме  готовности  к исполнению.

В простейшем случае данной реализации одиночный поток постоянно исполнял бы метод QueueProcessor (). Реализацию можно  оптимизировать,  создав  и  используя пул потоков. Пул потоков представляет собой коллекцию готовых к исполнению потоков. По мере прибытия задач потоки берутся из пула и применяются для выполнения  задач.  По  окончании  исполнения  задачи  поток  возвращается  оатно В пул потоков. В конструкторе ThreadPoolProducerConsumer метод ThreadPool .QueueUserWorkItem() ИСПОЛЬЗует ПОДХОД пула ПОТОКОВ ДЛЯ исполнения метода QueueProcessorо. В реализации  метода  QueueProcessor()  метод ThreadPool. Queueuserworkitem()  вызывается    снова    перед    вызовом    делегата. В результате один поток всегда ожидает на элемент в очереди, но элементы из очереди  могут обрабатываться  несколькими  потоками  одновременно.

Использование общего поставщика/потребителя почти идентично применению мода invoke ()  библиотеки windows.Forms. Далее приводится пример его реализации:

public class TestProducerConsumer { delegate void TestMethodO;

void Methodf) {

Console.WriteLine("Processed in thread id (" +

Thread.CurrentThread.ManagedThreadld + ")");

}

public void TestSimpleO {

IProducerConsumer producer = new ThreadPoolProducerConsumer();

Console.WriteLine("Sent in thread id (" +

Thread.CurrentThread.ManagedThreadld + ")"); producer.Invoke(new TestMethod(Method));

}

}

Метод TestSimple ()  создает экземпляр типа ThreadPoolProducerConsumer. ПОТОМ с помощью делегата TestMethod, исполняющего метод Method (), вызывается метод invoke (). В windows. Forms создается экземпляр другого типа, но используется тот же самый метод invoke (). Реализация также немного отличается тем, что потребель является не одним потоком, а несколькими потоками в необходимом количестве.

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

  • Комментарии