Почему в асинхронных методах нельзя использовать lock и какие есть альтернативы?
Что проверяют
Знаете ли вы, что lock привязан к потоку, а продолжение после await может выполниться в другом. И отличаете ли вы защиту данных от ограничения параллелизма.
Как отвечать
Сначала поправить формулировку: lock в асинхронном методе не запрещён, запрещён await внутри блока lock, и это ошибка компиляции. Причина в устройстве Monitor: владельцем замка является поток. После await продолжение может подхватить другой поток, и освобождать замок будет уже не тот, кто его брал. Плюс сам смысл теряется: удерживать замок на время сетевого вызова значит выстроить всех в очередь на время ввода-вывода.
Дальше по вариантам:
- короткая синхронная секция внутри асинхронного метода:
lockподходит и остаётся самым дешёвым решением, если внутри нетawait; - нужно дождаться доступа асинхронно:
SemaphoreSlim;
private readonly SemaphoreSlim gate = new(1, 1);
await gate.WaitAsync(ct);
try { await LoadAsync(ct); }
finally { gate.Release(); }- потокобезопасная коллекция:
ConcurrentDictionaryи соседи, часто вместо замка вовсе; - простое значение или счётчик:
Interlockedи неизменяемые объекты; - сериализация работы: очередь или
Channel<T>, где один потребитель разбирает задачи по одной, и синхронизация не нужна в принципе.
Где спотыкаются
Говорят, что lock в асинхронном коде запрещён совсем. Компилятор ругается только на await внутри блока, а короткая синхронная критическая секция там законна и обычно правильнее семафора.
Второе: берут SemaphoreSlim, но забывают Release в finally или входят повторно из вложенного вызова. Семафор не знает владельца и не реентрантен, в отличие от Monitor: повторный вход это вечное ожидание самого себя, а пропущенный Release вешает всех остальных.
Третье: предлагают Mutex как асинхронную альтернативу. Он тоже принадлежит захватившему потоку, освобождать его после await нельзя, и задача у него другая, межпроцессная синхронизация. Туда же попытки обойти проблему через .Result и .Wait(): в контексте с захватом это классическая взаимоблокировка.