Что такое lock в C# и как его использовать?
Что проверяют
Понимаете ли вы, что это синтаксис поверх Monitor, и знаете ли правила выбора объекта блокировки.
Как отвечать
lock обеспечивает, что критическую секцию одновременно выполняет только один поток. Компилятор превращает его в Monitor.Enter и Monitor.Exit в try/finally, поэтому блокировка снимается и при исключении.
private readonly object _sync = new();
public void Add(int x)
{
lock (_sync) { _items.Add(x); }
}Правила выбора объекта:
- приватный и только для чтения, чтобы никто снаружи не мог захватить тот же монитор;
- не
this: чужой код может заблокировать ваш экземпляр; - не строка и не тип: строки интернируются, а
typeof(T)общий на весь домен, и блокировка окажется глобальной; - не значимый тип: он упаковывается, и каждый раз получается новый объект, то есть блокировки не будет вовсе.
Что важно помнить: lock реентрантный, тот же поток может войти повторно. Внутри нельзя писать await, компилятор это запрещает: для асинхронного кода есть SemaphoreSlim с WaitAsync. Держать блокировку на время сетевого вызова нельзя. Два потока, захватывающие две блокировки в разном порядке, дают взаимную блокировку.
Альтернативы: Interlocked для счётчиков, конкурентные коллекции вместо ручной синхронизации, ReaderWriterLockSlim при большом перевесе чтений.
Где спотыкаются
Блокируются на this или на строке.
Пытаются написать await внутри lock.
Считают, что lock защищает объект: он защищает секцию кода, и толк есть, только если все обращения проходят через неё.
Оборачивают в lock обращения к ConcurrentDictionary, теряя всю его пользу.