Наряду с оператором lock для синхронизации потоков мы можем использовать мониторы, представленные классом System.Threading.Monitor. Для управления синхронизацией этот класс предоставляет следующие методы:
void Enter(object obj): получает в эксклюзивное владение объект, передаваемый в качестве параметра.
void Enter(object obj, bool acquiredLock): дополнительно принимает второй параметра - логическое значение, которое указывает, получено ли владение над объектом из первого параметра
void Exit(object obj): освобождает ранее захваченный объект
bool IsEntered(object obj): возвращает true, если монитор захватил объект obj
void Pulse (object obj): уведомляет поток из очереди ожидания, что текущий поток освободил объект obj
void PulseAll(object obj): уведомляет все потоки из очереди ожидания, что текущий поток освободил объект obj. После чего один из потоков из очереди ожидания захватывает объект obj.
bool TryEnter (object obj): пытается захватить объект obj. Если владение над объектом успешно получено, то возвращается значение true
bool Wait (object obj): освобождает блокировку объекта и переводит поток в очередь ожидания объекта. Следующий поток в очереди готовности объекта блокирует данный объект. А все потоки, которые вызвали метод Wait, остаются в очереди ожидания, пока не получат сигнала от метода Monitor.Pulse или Monitor.PulseAll, посланного владельцем блокировки.
Стоит отметить, что фактически конструкция оператора lock инкапсулирует в себе синтаксис использования мониторов. Например, в прошлой теме для синхронизации потоков применялся оператор 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); } } }
Фактически данный пример будет эквивалентен следующему коду:
int x = 0; object locker = new(); // объект-заглушка // запускаем пять потоков for (int i = 1; i < 6; i++) { Thread myThread = new(Print); myThread.Name = $"Поток {i}"; myThread.Start(); } void Print() { bool acquiredLock = false; try { Monitor.Enter(locker, ref acquiredLock); x = 1; for (int i = 1; i < 6; i++) { Console.WriteLine($"{Thread.CurrentThread.Name}: {x}"); x++; Thread.Sleep(100); } } finally { if (acquiredLock) Monitor.Exit(locker); } }
Метод Monitor.Enter принимает два параметра - объект блокировки и значение типа bool, которое указывает на результат блокировки (если он равен true, то
блокировка успешно выполнена). Фактически этот метод блокирует объект locker так же, как это делает оператор lock. А в блоке try...finally с помощью
метода Monitor.Exit
происходит освобождение объекта locker, если блокировка осуществлена успешно, и он становится доступным для других потоков.