Оператор lock
обеспечивает взаимное исключение (mutual exclusion) для критических секций кода:
private readonly object _lockObj = new object();
void ThreadSafeMethod()
{
lock (_lockObj)
{
// Критическая секция
}
}
Что происходит при выполнении:
Компилятор преобразует lock
в следующий код:
object __lockObj = _lockObj;
bool __lockTaken = false;
try
{
Monitor.Enter(__lockObj, ref __lockTaken);
// Критическая секция
}
finally
{
if (__lockTaken)
Monitor.Exit(__lockObj);
}
Структуры являются value-типами и при передаче в Monitor.Enter:
struct LockStruct { }
LockStruct lockStruct = new LockStruct();
// Поток 1:
lock (lockStruct) // Упаковка в объект A
{
// ...
}
// Поток 2:
lock (lockStruct) // Упаковка в объект B - ДРУГОЙ объект!
{
// Не будет ждать освобождения из потока 1
}
В современных версиях C# компилятор явно запрещает использование структур:
lock (new MyStruct()) // Ошибка CS0185
{
}
CS0185: "Тип 'MyStruct' не может быть использован как аргумент для монитора, так как не является ссылочным типом"
private readonly object _lockObj = new object();
lock (_lockObj) // Корректно
{
}
Для сложных сценариев:
private readonly Mutex _mutex = new Mutex();
_mutex.WaitOne();
try
{
// Критическая секция
}
finally
{
_mutex.ReleaseMutex();
}
int lockInt = 42;
object boxed = lockInt; // Явная упаковка
lock (boxed) // Технически работает, но опасно!
{
}
if (Monitor.TryEnter(_lockObj, TimeSpan.FromSeconds(1)))
{
try { /* работа */ }
finally { Monitor.Exit(_lockObj); }
}
else
{
// Обработка таймаута
}
lock использует мониторы CLR для потокобезопасности и требует ссылочных типов, так как value-типы упаковываются в новые объекты, нарушая синхронизацию. Для структур используйте обертки в object или альтернативные механизмы синхронизации.