26 Возможности библиотеки Task Parallel Library. Сигнальные сообщения

Сигнальные сообщения позволяют реализовать разные схемы синхронизации, как взаимное исключение, так и условную синхронизацию. При условной синхронизации поток блокируется в ожидании события, которое генерируется в другом потоке. Платформа .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});