Создание меню

Класс Monitor

Класс Interlocked

Синхронизация работы потоков

Синхронизацией работы потоков называется обеспечение корректной работы нескольких потоков с общими (разделяемыми) данными или ресурсами.

Синхронизация обеспечивается путем организации монопольного доступа одного из потоков на время работы с разделяемыми ресурсами, и блокирования доступа к разделяемым ресурсам на это время со стороны других потоков.

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

Средство блокировки встроено в язык С#, поэтому доступ ко всем объектам может быть синхронизирован. Синхронизация поддерживается ключевым словом lock.


Формат использования инструкции lock таков:

lock(object)

{

// Инструкции, подлежащие блокировки.

}

Здесь параметр object представляет собой ссылку на синхронизируемый объект.

Если нужно блокировать только один элемент кода, фигурные скобки можно опустить. Инструкция lock гарантирует, что указанный блок кода, защищенный блокировкой для данного объекта, может быть использован только потоком, который получает эту блокировку. Все другие потоки остаются заблокированными до тех пор, пока исполняемый код потока, получившего доступ к ресурсам, не покинет блок, защищенный блокировкой. Инструкция lock может применяться не только к объектам, а и к статическим функциям класса. Предположим, что в классе CA имеется статическая функция, объявленная следующим образом:

static void Min(int a, int b)

{

:

:

},

Тогда обращение к этой функции с применением блокировки:

 

lock(typeof(CA)) CA.Min(x,y);

 

Инструкция lock является встроенным средством языка C#. Для организации синхронизации потоков в пространстве имен System.Threading имеется также специальные классыMonitor и Interlocked.

 


 

Класс Interlocked предназначен для организации неделимых операций, таких как сложение, увеличение на единицу, уменьшение на единицу, сравнение и других. Для этого в этом классе определено ряд статических методов. Термин неделимый здесь означает, что если операция началась выполняться одним потоком, то процессорное время не будет передано другому потоку, пока эта операция не завершится полностью. Например, для определения используется ли объект - сервер объектами - клиентами, можно использовать счетчик, который увеличивается на единицу, клиентом, если он необходим клиенту и уменьшается на единицу в противном случае:

 

public class CountRef

{

private long refCount = 0;

public void AddReft()

{

Interlocked.Increment(ref refCount);

}

public void Release()

{

Interlocked.Decrement(ref refCount);

}

:

:

}

 

 

В классе Monitor определено несколько статических методов для организации синхронизации. Для блокировки некоторого объекта, необходимо вызвать метод Enter (), а чтобы снять блокировку — метод Exit ().

Эти методы имеют следующий формат:

public static void Enter(object syncOb)

public static void Exit(object syncOb)

Здесь syncOb— синхронизируемый объект. Если при вызове метода Enter() за-

данный объект недоступен, вызывающий поток будет ожидать до тех пор, пока объект не станет доступным. Блок lock "совершенно эквивалентен" вызову метода Enter () с последующим вызовом метода Exit ().

В классе Monitor определены еще три метода: Wait(), Pulse () и PulseAll).

Эти методы можно вызывать только внутри lock-блока кода.

Имеется два наиболее употребимых формата метода wait ():

public static bool Wait(object waitOb)

public static bool Wait(object waitOb, int milliseconds)

Здесь параметр waitOb означает объект, освобождаемый от блокировки.

Метод Wait() обычно вызывается тогда, когда поток, захвативший какой либо ресурс, не может продолжить выполнение кода внутри lock-блока, так как ему (потоку) необходим еще какой то ресурс, недоступный в данный момент времени. Тогда, для того чтобы не блокировать работу других потоков, вызывается метод Wait(). После вызова метода Wait() поток переходит в режим останова ("засыпает") и снимает блокировку с объекта waitOb, позволяя другому потоку использовать этот объект. Выйти из состояния останова поток может либо только когда другой поток входит в аналогичное состояние блокирования и вызывает метод Pulse () или PulseAll(), либо по истечению определенного времени, которое указывается при вызове метода Wait в качестве второго параметра milliseconds.

Вызов метода Pulse () возобновляет выполнение потока, стоящего первым в очереди потоков, пребывающих в режиме ожидания. Вызов метода PulseAll () сообщает о возобновлении выполнения всех ожидающих потоков.

Форматы использования методов Pulse () и PulseAll () таковы:

public s t a t i c void Pulse(object waitOb)

public s t a t i c void PulseAll(object waitOb)

Если метод Wait (), Pulse () или PulseAll () вызывается из кода, который находится вне lock-блока, генерируется исключение типа SynchronizationLockException.

Для демонстрации работы методов lock(), Wait () и Pulse () рассмотрим пример, в котором объявлен класс Say , содержащий функцию Answer();

В качестве параметра функция Answer() принимает строку символов. Если эта строка "YES", то выводится строка ” ДА”, если “NO”, то “НЕТ”.

Для создания потока объявлен потоковый класс с именем MyThread. Конструктор класса принимает два параметра, первый – имя потока, а второй ссылку на объект типа Say. В конструкторе класса создается и запускается поток. В потоке выполняется функция этого класса FnThr(), которая в цикле обращается к функции Answer() объекта типа Say, сообщая ей при каждом обращении имя потока. В функции Main() создаются два потока с именами YES и NO, которые при своей работе используют общий объект sf класса Say.

Пример:

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;

 

namespace ConsoleApplication14

{

class Say

{

 

public void Answer(string name)

{

lock (this) //блокируем объект

{

if (name == "YES")

{

Console.Write("ДА ");

// будим поток, ожидающий освобождения объекта блокировки

Monitor.Pulse(this);

Monitor.Wait(this); //освобождаем объект блокировки и засыпаем

}

 

if (name == "NO")

{

Console.WriteLine("НЕТ");

// будим поток, ожидающий освобождения объекта блокировки

Monitor.Pulse(this);

Monitor.Wait(this); //освобождаем объект блокировки и засыпаем

 

}

}

}

}

class MyThread

{

Say say;

public Thread thr;

public MyThread(string nm, Say s)

{

say = s;

thr = new Thread(new ThreadStart(FnThr));

thr.Name = nm;

thr.Start();

}

void FnThr()

{

for (int j = 0; j < 10; j++)

{

say.Answer(thr.Name);

}

}

}

class Program

{

 

static void Main(string[] args)

{

Say sf = new Say();

MyThread thr1 = new MyThread("YES",sf);

MyThread thr2 = new MyThread("NO", sf);

}

}

}

Результат работы программы:

ДА НЕТ

ДА НЕТ

ДА НЕТ

ДА НЕТ

ДА НЕТ

ДА НЕТ

ДА НЕТ

ДА НЕТ

ДА НЕТ

ДА НЕТ

Модифицируем немного функцию Answer(), а именно – закоментируем все строчки Monitor.Pulse(this);

Логично предположить, что потоки выполнив свою работу один раз заснут навсегда. Запустим программу и удостоверимся, что это на самом деле так. Результат работы программы всего одна строчка:

 

ДА НЕТ

 

Если же закомментировать в функции Answer()еще и строки Monitor.Wait(this);, то очевидно, что поток, захватив объект синхронизации, не освободит его пока не выполнит свою работу до конца. Результат выполнения программы подтверждает это:

ДА ДА ДА ДА ДА ДА ДА ДА ДА ДА НЕТ

НЕТ

НЕТ

НЕТ

НЕТ

НЕТ

НЕТ

НЕТ

НЕТ

НЕТ

Для продолжения нажмите любую клавишу . . .

 

Вопросы:

1. Что называется синхронизацией работы потоков?

2. Каким образом обеспечивается синхронизация работы потоков?

3. Что лежит в основе синхронизации потоков?

4. Каким образом можно применить защиту блокировкой к статическому методу класса?

5. Что означает термин не делимый применительно к выполняемой инструкции кода?

6. Как применяются статические методы Enter () Exit () класса Monitor для организации блокировки?

7. Можно ли вызывать методы Wait (), Pulse () и PulseAll () вне lock-блока?

8. В каких случаях необходим вызов метода Wait ()?

9. В каких случаях необходим вызов методов Pulse () и PulseAll ()?


 

 

Меню, находящееся между заголовком и клиентской областью окна приложения, называется главным меню. Кроме главного меню существует другой вид меню контекстные меню или их еще называют меню быстрого вызова. Пункты в видимой части главного меню называются пунктами верхнего уровня. Выбор любого пункта верхнего уровня главного меню приводит к появлению в виде прямоугольника подменю или другими словами дочернего меню. Подменю в свою очередь состоит из нескольких пунктов. В общем случае меню верхнего уровня представляет собой массив пунктов меню, а каждый пункт меню – массив пунктов подменю.

Для создания меню используются классы MainMenu, ContextMenu и MenuItem, порожденные от абстрактного класса Menu. В классе Menu объявлен внутренний класс MenuItemCollection, который наследуется классами MainMenu, ContextMenu и MenuItem.