Вопрос исследует конфликт между асинхронным кодом и блокировками, а также внутреннюю работу lock.
await внутри lock может вызвать взаимоблокировку или нарушить поток выполнения. После await поток меняется, и блокировка не освобождается автоматически. lock компилируется в Monitor.Enter/Exit с try/finally, чтобы гарантировать освобождение блокировки даже при исключениях.
Проблемы с await в lock:
Смена потока:
lock требует, чтобы один поток владел блокировкой от входа до выхода.
После await выполнение может возобновиться в другом потоке, который не имеет прав на блокировку.
Риск взаимоблокировки (deadlock):
Пример:
lock (obj) {
await Task.Delay(1000); // Блокировка НЕ освобождается
// Другой поток может зависнуть, ожидая `obj`
}Как работает lock под капотом:
Код:
lock (obj) { /* тело */ }Компилируется в:
object __lockObj = obj;
bool __lockTaken = false;
try {
Monitor.Enter(__lockObj, ref __lockTaken);
/* тело */
}
finally {
if (__lockTaken) Monitor.Exit(__lockObj);
}Альтернативы для асинхронного кода:
Используйте SemaphoreSlim с асинхронными методами:
private readonly SemaphoreSlim semaphore = new(1, 1);
// ...
await semaphore.WaitAsync();
try { /* работа */ }
finally { semaphore.Release(); }Когда использовать:lock — для синхронного кода. Для асинхронных сценариев применяйте SemaphoreSlim, Mutex или другие примитивы.