Класс AutoResetEvent также служит целям синхронизации потоков. Этот класс представляет событие синхронизации потоков, который позволяет при получении сигнала переключить данный объект-событие из сигнального в несигнальное состояние.
Для управления синхронизацией класс AutoResetEvent предоставляет ряд методов:
Reset(): задает несигнальное состояние объекта, блокируя потоки.
Set();: задает сигнальное состояние объекта, позволяя одному или нескольким ожидающим потокам продолжить работу.
WaitOne(): задает несигнальное состояние и блокирует текущий поток, пока текущий объект AutoResetEvent не получит сигнал.
Событие синхронизации может находиться в сигнальном и несигнальном состоянии. Если состояние события несигнальное, поток, который вызывает метод WaitOne, будет заблокирован, пока состояние события не станет сигнальным. Метод Set, наоборот, задает сигнальное состояние события.
Так, в одной из предыдущих тем для синхронизации потоков применялся оператор lock:
int x = 0; object locker = new(); // объект-заглушка // запускаем пять потоков for (int i = 1; i < 6; i++) { Thread myThread = new(Print); myThread.Name = $"Поток {i}"; myThread.Start(); } void Print() { lock (locker) { x = 1; for (int i = 1; i < 6; i++) { Console.WriteLine($"{Thread.CurrentThread.Name}: {x}"); x++; Thread.Sleep(100); } } }
Перепишем этот пример с использованием AutoResetEvent:
int x = 0; // общий ресурс AutoResetEvent waitHandler = new AutoResetEvent(true); // объект-событие // запускаем пять потоков for (int i = 1; i < 6; i++) { Thread myThread = new(Print); myThread.Name = $"Поток {i}"; myThread.Start(); } void Print() { waitHandler.WaitOne(); // ожидаем сигнала x = 1; for (int i = 1; i < 6; i++) { Console.WriteLine($"{Thread.CurrentThread.Name}: {x}"); x++; Thread.Sleep(100); } waitHandler.Set(); // сигнализируем, что waitHandler в сигнальном состоянии }
Во-первых, создаем переменную типа AutoResetEvent
. Передавая в конструктор значение true
, мы тем самым указываем,
что создаваемый объект изначально будет в сигнальном состоянии.
Когда начинает работать поток, то первым делом срабатывает определенный в методе Print вызов waitHandler.WaitOne()
. Метод
WaitOne указывает, что текущий поток переводится в состояние ожидания, пока объект waitHandler не будет переведен в сигнальное состояние.
И так все потоки у нас переводятся в состояние ожидания.
После завершения работы вызывается метод waitHandler.Set
, который уведомляет все ожидающие потоки, что объект waitHandler снова находится в сигнальном состоянии,
и один из потоков "захватывает" данный объект, переводит в несигнальное состояние и выполняет свой код. А остальные потоки снова ожидают.
Так как в конструкторе AutoResetEvent мы указываем, что объект изначально находится в сигнальном состоянии, то первый из очереди потоков захватывает данный объект и начинает выполнять свой код.
Но если бы мы написали AutoResetEvent waitHandler = new AutoResetEvent(false)
, тогда объект изначально был бы в несигнальном состоянии,
а поскольку все потоки блокируются методом waitHandler.WaitOne()
до ожидания сигнала, то у нас попросту случилась бы блокировка программы, и программа
не выполняла бы никаких действий.
Если у нас в программе используются несколько объектов AutoResetEvent
, то мы можем использовать для отслеживания состояния этих объектов
статические методы WaitAll и WaitAny, которые в качестве параметра принимают массив объектов класса WaitHandle - базового класса
для AutoResetEvent.
Так, мы тоже можем использовать WaitAll в вышеприведенном примере. Для этого надо строку
waitHandler.WaitOne();
заменить на следующую:
AutoResetEvent.WaitAll(new WaitHandle[] {waitHandler});