Какие проблемы могут возникнуть при использовании await внутри lock в C#
Что проверяют
Знаете ли вы, что await внутри lock это не «плохая практика», а ошибка компиляции, и понимаете ли, почему компилятор её ставит. Дальше обычно просят показать, чем заменить блокировку в асинхронном коде, и объяснить, чем SemaphoreSlim отличается от Monitor по поведению.
Как отвечать
Начните с факта: код с await в теле lock не соберётся, компилятор выдаёт CS1996 «Cannot await in the body of a lock statement». Причина в устройстве Monitor: владение блокировкой привязано к потоку, а продолжение после await может выполниться на любом потоке пула. Освободить блокировку, взятую чужим потоком, невозможно.
Дальше про обходы, которые как раз и ломаются:
- ручные
Monitor.EnterиMonitor.Exitвокругawait: компилятор молчит, а в рантайме прилетаетSynchronizationLockException, потому чтоExitзовёт уже другой поток; .Resultили.Wait()внутриlock: поток блокируется, продолжая держать замок. В окружении с однопоточнымSynchronizationContext(классический ASP.NET, WinForms, WPF) это готовый дедлок;lockвокруг синхронной обёртки над асинхронным методом: та же ловушка, просто спрятанная на уровень глубже.
Правильный инструмент это SemaphoreSlim(1, 1) с await sem.WaitAsync() и обязательным Release() в finally. Ещё варианты: Channel<T> или очередь задач, когда нужен сериализованный доступ; Interlocked и ConcurrentDictionary там, где хватает атомарной операции; AsyncLock из Nito.AsyncEx.
Упомяните, что в C# 13 появился тип System.Threading.Lock с методом EnterScope(), который оператор lock распознаёт особо. Запрета на await это не отменяет.
Где спотыкаются
Уверенно рассказывают про дедлок при await внутри lock, не зная, что такой код просто не компилируется. Это сразу выдаёт теорию по статьям вместо практики.
Забывают, что SemaphoreSlim не реентерантный. Monitor пускает тот же поток повторно, семафор нет: вложенный вызов метода, который берёт тот же семафор, повесит задачу навсегда.
Теряют Release() при исключении. Без try/finally или своей обёртки с using первая же ошибка внутри критической секции закрывает доступ для всех остальных.