Сигнальные сообщения позволяют реализовать разные схемы синхронизации, как взаимное исключение, так и условную синхронизацию. При условной синхронизации поток блокируется в ожидании события, которое генерируется в другом потоке. Платформа .NET предоставляет три типа сигнальных сообщений:
- AutoResetEvent,
- ManualResetEvent
- и ManualResetEventSlim,
- а также шаблоны синхронизации, построенные на сигнальных сообщениях (CountdownEvent, Barrier).
Первые два типа построены на объекте ядра операционной системы. Третий тип ManualResetEventSlim является облегченной версией объекта ManualResetEvent, является более производительным.
В следующем фрагменте два потока используют один и тот же объект типа ManualResetEvent. Первый поток выводит сообщение от второго потока. Сообщение записывается в разделяемую переменную. Вызов метода WaitOne блокирует первый поток в ожидании сигнала от второго потока. Сигнал генерируется при вызове метода Set.
void OneThread(object o) { ManualResetEvent mre = (ManualResetEvent)o; mre.WaitOne(); Console.WriteLine("Data from thread #2: " + data); } void SecondThread(object o) { ManualResetEvent mre = (ManualResetEvent)o; Console.WriteLine("Writing data"); data = "BBBBBB"; mre.Set(); }
Вывод:
Writing data..
Data from thread#2: BBBB
Отличия инструментов AutoResetEvent и ManualResetEvent заключаются в режиме сброса статуса сигнального события: автоматическое (auto reset) или ручное (manual reset). Сигнал с автоматическим сбросом снимается сразу же после освобождения потока, блокированного вызовом WaitOne. Сигнал с ручным сбросом не снимается до тех пор, пока какой-либо поток не вызовет метод Reset.
В следующем фрагменте рассматриваются отличия сигнальных сообщений. Управляющий поток Manager запускает пять рабочих потоков и каждому передает один и тот же сигнальный объект. Рабочие потоки ожидают сигнала от управляющего потока.
void Worker(object initWorker) { string name = ((object[])initWorker)[0] as string; ManualResetEvent mre = (object[])initWorker)[1] as ManualResetEvent; // Waiting to start work mre.WaitOne(); Console.WriteLine("Worker {0} starts ..", name); // useful work } void Manager() { int nWorkers = 5; Thread[] worker = new Thread[nWorkers]; ManualResetEvent mre = new ManualResetEvent(false); for(int i=0; i<nWorkers; i++) { worker[i] = new Thread(Worker); worker[i].Start(new object[]{"#" + i, mre}); } // preparing data in shared variables for workers // let start work mre.Set(); }
При установлении события mre работу начнут все ожидающие рабочие потоки. При замене объекта на AutoResetEvent событие будет сбрасываться автоматически и "поймает" его только какой-то один поток. Таким образом, объект AutoResetEvent можно использовать для реализации взаимно исключительного доступа.
static void ThreadFunc(object o) { var lockEvent = o as AutoResetEvent; ParallelWork(); lockEvent.WaitOne(); CriticalWork(); lockEvent.Set(); } static void Main() { Thread[] workers = new Thread[5]; for(int i=0; i<5; i++) workers[i] = new Thread(ThreadFunc); var lockEvent = new AutoResetEvent(true); for(int i=0; i<5; i++) workers[i].Start(lockEvent); }
В этом примере пять рабочих потоков часть работы могут выполнять параллельно, но какой-то фрагмент должны выполнять последовательно (критическая секция). Сообщение типа AutoResetEvent используется для организации взаимно-исключительного доступа к критической секции. Объект инициализируется с установленным сигналом для того, чтобы вначале работы один из потоков вошел в критическую секцию. При завершении выполнения критической секции поток дает сигнал одному из ожидающих потоков. Порядок вхождения потоков в критическую секцию, также как и при использовании объекта Monitor, не определен.
Объект ManualResetEventSlim функционально соответствует сигнальному событию с ручным сбросом. Применение гибридной блокировки повышает производительность в сценариях с малым временем ожидания. Вызов метода Wait в течение ограниченного промежутка времени сохраняет поток в активном состоянии (циклическая проверка статуса сигнала), если сигнал не поступил, то осуществляется вызов дескриптора ожидания ядра операционной системы.
Объекты AutoResetEvent, ManualResetEvent, а также объекты Semaphore, Mutex происходят от объекта, инкапсулирующего дескриптор ожидания ядра WaitHandle. Тип WaitHandle содержит полезные статические методы ожидания нескольких объектов синхронизации ядра:
var ev1 = new ManualResetEvent(false); var ev2 = new ManualResetEvent(false); new Thread(SomeFunc).Start(ev1); new Thread(SomeFunc).Start(ev2); // Ожидаем все сигналы WaitHandle.WaitAll(new ManualResetEvent[] {ev1, ev2}); ev1.Reset(); ev2.Reset(); // Ожидаем хотя бы один сигнал int iFirst = WaitHandle.WaitAny(new ManualResetEvent[] {ev1, ev2});