Мониторы
Наряду с оператором 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, если блокировка осуществлена успешно, и он становится доступным для других потоков.