Монитор против блокировки

Когда целесообразно использовать либо класс Monitor, либо ключевое слово lock для безопасности потоков в С#?

EDIT: По-видимому, из ответов до сих пор, что lock является короткой рукой для серии вызовов класса Monitor. Для чего нужен замок? Или более явно,

class LockVsMonitor
{
    private readonly object LockObject = new object();
    public void DoThreadSafeSomethingWithLock(Action action)
    {
        lock (LockObject)
        {
            action.Invoke();
        }
    }
    public void DoThreadSafeSomethingWithMonitor(Action action)
    {
        // What goes here ?
    }
}

Обновление

Спасибо всем за вашу помощь. Я отправил еще один вопрос в качестве ответа на некоторые из предоставленной вами информации. Поскольку вы, похоже, хорошо разбираетесь в этой области, я опубликовал ссылку: Что не так с этим решением для блокировки и управления заблокированными исключениями?

Ответ 1

Эрик Липпер об этом говорит в своем блоге: Замки и исключения не смешиваются

Эквивалентный код отличается от С# 4.0 и более ранних версий.


В С# 4.0 это:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    { body }
}
finally
{
    if (lockWasTaken) Monitor.Exit(temp);
}

Он полагается на Monitor.Enter атомарно устанавливая флаг при блокировке.


И раньше это было:

var temp = obj;
Monitor.Enter(temp);
try
{
   body
}
finally
{
    Monitor.Exit(temp);
}

Это зависит от того, что между Monitor.Enter и try не выбрано исключение. Я думаю, что в коде отладки это условие было нарушено, потому что компилятор вставил NOP между ними и, таким образом, сделал прерывание потока между этими возможными.

Ответ 2

lock является просто ярлыком для Monitor.Enter с try + finally и Monitor.Exit. Используйте инструкцию блокировки всякий раз, когда этого достаточно - если вам нужно что-то вроде TryEnter, вам придется использовать Monitor.

Ответ 3

Оператор блокировки эквивалентен:

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Однако помните, что Monitor также может Wait() и Pulse(), которые часто полезны в сложных ситуациях многопоточности.

Обновление

Однако в С# 4 он реализован по-разному:

bool lockWasTaken = false;
var temp = obj;
try 
{
     Monitor.Enter(temp, ref lockWasTaken); 
     //your code
}
finally 
{ 
     if (lockWasTaken) 
             Monitor.Exit(temp); 
} 

Thanx to CodeInChaos для комментариев и ссылки

Ответ 4

Monitor более гибкий. Мой любимый вариант использования монитора - , когда вы не хотите ждать своей очереди и просто пропустите:

//already executing? forget it, lets move on
if(Monitor.TryEnter(_lockObject))
{
    //do stuff;
    Monitor.Exit(_lockObject);
}

Ответ 5

Как говорили другие, lock эквивалентен

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Но только из любопытства lock сохранит первую ссылку, которую вы передадите ей, и не будет бросать, если вы ее измените. Я знаю, что не рекомендуется менять заблокированный объект, и вы не хотите этого делать.

Но опять же, для науки это прекрасно работает:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        lock (lockObject)
        {
            lockObject += "x";
        }
    }));
Task.WaitAll(tasks.ToArray());

... И это не так:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        Monitor.Enter(lockObject);
        try
        {
            lockObject += "x";
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }));
Task.WaitAll(tasks.ToArray());

Ошибка:

Исключение типа 'System.Threading.SynchronizationLockException' произошел в 70783sTUDIES.exe, но не был обработан в коде пользователя.

Дополнительная информация: метод синхронизации объектов вызывается из несинхронизированный блок кода.

Это связано с тем, что Monitor.Exit(lockObject); будет действовать на lockObject, который изменился, потому что strings неизменяемы, тогда вы вызываете его из несинхронизированного блока кода.. но в любом случае. Это просто забавный факт.

Ответ 7

Блокировка и основное поведение монитора (ввод + выход) более или менее одинаковы, но у монитора больше опций, что позволяет вам больше возможностей синхронизации.

Блокировка - это ярлык, и это опция для основного использования.

Если вам нужно больше контроля, монитор лучше. Вы можете использовать Wait, TryEnter и Pulse для расширенного использования (например, барьеры, семафоры и т.д.).