Как работает Monitor в C#?
Что проверяют
Понимаете ли вы, что стоит за lock, и умеете ли пользоваться Wait и Pulse без гонок. Следом обычно спрашивают, почему внутри lock нельзя написать await и чем его заменить.
Как отвечать
Monitor это статический класс из System.Threading для взаимного исключения на объекте. Оператор lock (obj) { ... } компилятор разворачивает в Monitor.Enter(obj, ref lockTaken) и try/finally с Monitor.Exit(obj), поэтому блокировка освобождается даже при исключении.
- Enter и Exit: захват и освобождение. Монитор реентерабельный: тот же поток может войти повторно и должен выйти столько же раз. Освободить блокировку, которой поток не владеет, нельзя, будет
SynchronizationLockException; - TryEnter: попытка с таймаутом, возвращает
bool; - Wait: отпускает блокировку, ставит поток в очередь ожидания, а после пробуждения снова её захватывает;
- Pulse и PulseAll: переводят один или все ждущие потоки в очередь на захват. Все три метода вызываются только под блокировкой того же объекта.
Под капотом CLR сначала пытается взять тонкую блокировку в заголовке объекта атомарной операцией и немного крутится в цикле, и только при реальной конкуренции заводит sync block и усыпляет поток.
await внутри lock запрещён компилятором: продолжение может выполниться в другом потоке, а монитор привязан к потоку-владельцу. Для асинхронного кода берут SemaphoreSlim с WaitAsync, для синхронизации между процессами Mutex. Хорошо упомянуть, что в .NET 9 появился тип System.Threading.Lock, и lock по объекту этого типа в C# 13 работает через него, а не через Monitor.
Где спотыкаются
Блокируются на this, на typeof(...) или на строке. Такой объект доступен чужому коду, и посторонний lock на нём даёт неожиданную взаимоблокировку. Нужно приватное поле readonly object.
Передают в Monitor.Enter значимый тип. Он упаковывается в новый объект при каждом вызове, поэтому Exit получит другой экземпляр и бросит исключение.
Вызывают Wait под if, а не в цикле while. Между Pulse и повторным захватом блокировки другой поток может успеть изменить состояние, и без повторной проверки код пойдёт дальше с неверными данными.