Ответ
Нельзя. Это классическая ошибка, ведущая к взаимоблокировкам (deadlock) и снижению производительности.
Почему это опасно:
Примитивы синхронизации высшего уровня (ManualResetEvent, SemaphoreSlim, Mutex, AutoResetEvent) уже являются потокобезопасными и internally используют свои собственные механизмы синхронизации ядра ОС или пользовательского режима. Обёртывание их в lock создаёт избыточную и конфликтующую блокировку.
Пример Deadlock:
private object _lockObj = new object();
private ManualResetEvent _mre = new ManualResetEvent(false);
// Поток A
lock (_lockObj) // Захватывает _lockObj
{
_mre.WaitOne(); // Блокируется, ожидая сигнала
}
// Поток B (который должен подать сигнал)
lock (_lockObj) // Вечно ждёт, пока Поток A освободит _lockObj -> DEADLOCK
{
_mre.Set();
}
Правильный подход:
-
Используйте примитив напрямую. Его API спроектирован для безопасного вызова из нескольких потоков.
private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); public async Task AccessResourceAsync() { await _semaphore.WaitAsync(); // Достаточно одной операции try { // Критическая секция } finally { _semaphore.Release(); } } - Выбирайте примитив под задачу:
SemaphoreSlim— для ограничения одновременного доступа к ресурсу.ManualResetEventSlim— для уведомления потоков о событии.ReaderWriterLockSlim— для оптимизации сценариев "много чтений / редко запись".lock(Monitor) — для быстрой низкоуровневой синхронизации простых операций над объектом.
Ответ 18+ 🔞
Да ты посмотри, что люди вытворяют! Ну реально, как будто в первый раз за компом сидят. Обернуть ManualResetEvent в lock — это ж как надеть два презерватива сразу: вроде и надёжнее, но нихуя не работает, а только рвутся оба.
Смотри, в чём тут прикол-то. Эти штуки — SemaphoreSlim, Mutex, всякие ивенты — они уже внутри себя потокобезопасные дохуя. У них там своя, отдельная от твоего кода, магия синхронизации от операционки или рантайма. А ты берёшь и сверху ещё свой lock натягиваешь. Получается как два мужика в одной будке: один дверь держит, второй тоже дверь держит, оба орут «Я первый!», а посрать нихуя не могут.
Вот тебе классический пиздец, который получится:
private object _lockObj = new object(); // Наш "замок"
private ManualResetEvent _mre = new ManualResetEvent(false); // Семафорчик
// Поток №1, Вася
lock (_lockObj) // Вася захватил замок, молодец
{
_mre.WaitOne(); // И... заснул, ждёт сигнала
}
// Поток №2, Петя (должен разбудить Васю)
lock (_lockObj) // Петя пытается взяться за замок... а он у Васи!
{
_mre.Set(); // Эту строчку Петя никогда не выполнит. ВСЁ, ПИЗДЕЦ.
}
Вася спит и держит замок. Петя ждёт замок, чтобы Васе сигнал подать. Взаимная блокировка, deadlock, зависание нахуй, перезагрузка сервера в три часа ночи и разборки с админами.
Как делать надо, чтобы не было стыдно:
-
Используй инструмент по назначению, блядь. Если взял
SemaphoreSlim— работай с ним напрямую, он для этого и создан.private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); // Один пропуск public async Task DoWorkAsync() { await _semaphore.WaitAsync(); // Взял пропуск — и всё, ты в безопасности try { // Делай тут что хочешь, ты один } finally { _semaphore.Release(); // Отдал пропуск другому } } -
Выбирай, чем душить, с умом:
SemaphoreSlim— если нужно пускать к ресурсу, например, только 5 потоков одновременно, а остальные пусть ждут.ManualResetEventSlim— чтобы крикнуть спящим потокам «Просыпайтесь, суки, данные готовы!».ReaderWriterLockSlim— когда у тебя тысяча потоков читает, а один раз в час что-то пишет. Нехуя всем ждать одного писателя.- Обычный
lock— для простых, быстрых операций, типа «инкрементировать счётчик» или «добавить в список». Не тащи в него асинхронные ожидания, ёпта!
Запомни, как «Отче наш»: не создавай лишних слоёв синхронизации. Это не одеяло, чтобы укутываться. Каждый лишний lock — это потенциальная точка для deadlock'а и гарантированный удар по производительности. Компьютер и так еле дышит, а ты его ещё своими костылями добиваешь.