Остается ли заблокированный объект заблокированным, если внутри него возникает исключение?

87

В приложении для потоковой передачи С #, если бы я заблокировал объект, скажем, очередь, и если произойдет исключение, останется ли объект заблокированным? Вот псевдокод:

int ii;
lock(MyQueue)
{
   MyClass LclClass = (MyClass)MyQueue.Dequeue();
   try
   {
      ii = int.parse(LclClass.SomeString);
   }
   catch
   {
     MessageBox.Show("Error parsing string");
   }
}

Насколько я понимаю, код после перехвата не выполняется - но мне было интересно, будет ли освобождена блокировка.

Khadaji
источник
1
В качестве последней мысли (см. Обновления) - вероятно, вам следует удерживать блокировку только на время удаления из очереди ... выполнять обработку вне блокировки.
Марк Грейвелл
1
Код после перехвата выполняется, потому что исключение обрабатывается
cjk
Спасибо, я, должно быть, пропустил этот вопрос, мне удалить этот вопрос?
Vort3x
4
Кажется, что пример кода не подходит для этого вопроса, но вопрос вполне правильный.
SalvadorGomez
Автор C # Designer - Блокировка и исключение
Дживан

Ответы:

91

Первый; вы рассматривали TryParse?

in li;
if(int.TryParse(LclClass.SomeString, out li)) {
    // li is now assigned
} else {
    // input string is dodgy
}

Блокировка будет снята по 2 причинам; во-первых, lockпо сути:

Monitor.Enter(lockObj);
try {
  // ...
} finally {
    Monitor.Exit(lockObj);
}

Второй; вы перехватываете и не lockгенерируете повторно внутреннее исключение, поэтому на самом деле оно никогда не видит исключения. Конечно, вы удерживаете блокировку на время MessageBox, что может быть проблемой.

Таким образом, он будет выпущен во всех, кроме самых фатальных, катастрофических и невосстановимых исключений.

Марк Гравелл
источник
15
Я знаю о трипарсе, но он не имеет отношения к моему вопросу. Это был простой код, объясняющий вопрос, а не настоящая проблема, связанная с синтаксическим анализом. Пожалуйста, замените синтаксический анализ любым кодом, который заставит вас уловить и сделает вас удобнее.
Khadaji
13
Как насчет выброса нового исключения («в иллюстративных целях»); ;-p
Марк
1
За исключением случаев, когда TheadAbortExceptionмежду Monitor.Enterи try: blogs.msdn.com/ericlippert/archive/2009/03/06/…
Игал Табачник
2
«фатальные катастрофические невосстановимые исключения», например, переход через ручьи.
Фил Купер,
99

Замечу, что никто не упомянул в своих ответах на этот старый вопрос, что снятие блокировки исключения - невероятно опасная вещь. Да, операторы блокировки в C # имеют семантику «finally»; когда элемент управления выходит из замка нормально или ненормально, замок снимается. Вы все говорите об этом так, будто это хорошо, но это плохо! Правильный поступок, если у вас есть заблокированная область, которая вызывает необработанное исключение, - это немедленно завершить зараженный процесс до того, как он уничтожит больше пользовательских данных , а не освобождать блокировку и продолжать работу. .

Посмотрите на это так: представьте, что у вас есть ванная с замком на двери и очередь людей, ожидающих снаружи. Бомба в ванной взорвалась, убив человека там. Ваш вопрос: «Будет ли в этой ситуации замок автоматически разблокирован, чтобы следующий человек мог попасть в ванную комнату?» Да, это будет. Это нехорошо. Там просто взорвалась бомба и кого-то убили! Водопровод, вероятно, разрушен, дом больше не является прочным, и там может быть еще одна бомба . Правильнее всего вывести всех как можно быстрее и снести весь дом.

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

Если вы заблокировали область кода, чтобы выполнить мутацию без одновременного чтения состояния другим потоком, и мутация выдает ошибку, то, если данные не были повреждены раньше, это точно теперь . Это именно тот сценарий, от которого должна защищать блокировка . Теперь коду, который ожидает чтения этого состояния, немедленно будет предоставлен доступ к поврежденному состоянию и, возможно, произойдет сбой. Опять же, правильное решение - прекратить процесс.

Как ни крути, исключение внутри блокировки - плохая новость . Правильный вопрос - не "будет ли моя блокировка очищена в случае исключения?" Правильный вопрос, который следует задать: «Как мне гарантировать, что внутри блокировки никогда не будет исключения? И если есть, то как мне структурировать мою программу так, чтобы мутации возвращались к предыдущему хорошему состоянию?»

Эрик Липперт
источник
19
Эта проблема довольно ортогональна блокировке IMO. Если вы получаете ожидаемое исключение, вы хотите очистить все, включая блокировки. А если вы получаете неожиданное исключение, у вас есть проблема, с блокировками или без них.
CodesInChaos
10
Я считаю, что описанная выше ситуация является обобщением. Иногда исключения описывают катастрофические события. Иногда этого не происходит. Каждый использует их в коде по-разному. Совершенно верно, что исключение является сигналом об исключительном, но не катастрофическом событии - при условии, что исключения = катастрофические, случай завершения процесса слишком специфичен. Тот факт, что это могло быть катастрофическим событием, не отменяет обоснованности вопроса - тот же ход мыслей может привести вас к тому, что вы никогда не обработаете какое-либо исключение, и в этом случае процесс будет завершен ...
Герасимос Р.
1
@GerasimosR: Верно. Следует подчеркнуть два момента. Во-первых, исключения следует рассматривать как катастрофические до тех пор, пока они не будут определены как благоприятные. Во-вторых, если вы получаете безопасное исключение из заблокированной области, то заблокированная область, вероятно, плохо спроектирована; вероятно, он слишком много работает внутри замка.
Эрик Липперт
1
При всем уважении, но похоже, что вы говорите, мистер Липперт, что дизайн C # блока блокировки, который под капотом использует оператор finally, - это плохо. Вместо этого мы должны полностью вывести из строя каждое приложение, которое когда-либо имело исключение в операторе блокировки, которое не было обнаружено в блокировке. Может быть, вы не об этом говорите, но если это так, я действительно рад, что команда пошла по тому пути, который они выбрали, а не по вашему (экстремистскому из соображений пуристов!). Всем уважении.
Николас Петерсен
2
В случае, если неясно, что я имею в виду под несоставным: предположим, что у нас есть метод «передачи», который принимает два списка, s и d, блокирует s, блокирует d, удаляет элемент из s, добавляет элемент в d, разблокирует d, открывает s. Метод является правильным , если никто никогда не пытается передать из списка X в список Y в то же время , как кто - то еще пытается передать из Y в X . Правильность метода передачи не позволяет вам построить на его основе правильное решение более крупной проблемы, потому что блокировки - это небезопасные мутации глобального состояния. Для безопасного «переноса» вы должны знать о каждой блокировке в программе.
Эрик Липперт
43

да, это выпустит правильно; lockдействует как try/ finallyс the Monitor.Exit(myLock)в finally, поэтому независимо от того, как вы выйдете, он будет выпущен. В качестве примечания catch(... e) {throw e;}лучше избегать, так как это повреждает трассировку стека e; лучше не поймать его на всех , или в качестве альтернативы: использование , throw;а не throw e;который делает повторный бросок.

Если вы действительно хотите знать, блокировка в C # 4 / .NET 4:

{
    bool haveLock = false;
    try {
       Monitor.Enter(myLock, ref haveLock);
    } finally {
       if(haveLock) Monitor.Exit(myLock);
    }
} 
Марк Гравелл
источник
14

"Оператор блокировки компилируется для вызова Monitor.Enter, а затем для блока try… finally. В блоке finally вызывается Monitor.Exit.

Генерация JIT-кода как для x86, так и для x64 гарантирует, что прерывание потока не может произойти между вызовом Monitor.Enter и блоком try, который непосредственно следует за ним ".

Взято с: Этот сайт

GH.
источник
1
Существует по крайней мере один случай, когда это не так: прерывание потока в режиме отладки в версиях .net до 4. Причина в том, что компилятор C # вставляет NOP между Monitor.Enterи try, так что условие «немедленно следует» для JIT нарушен.
CodesInChaos
6

Ваш замок будет разблокирован правильно. А lockдействует так:

try {
    Monitor.Enter(myLock);
    // ...
} finally {
    Monitor.Exit(myLock);
}

И finallyблоки гарантированно будут выполнены, независимо от того, как вы выходите из tryблока.

Ry-
источник
Фактически, никакого кода не «гарантировано» (например, вы можете выдернуть шнур питания), и это не совсем то, как выглядит блокировка в 4.0 - см. Здесь
Марк Гравелл
1
@MarcGravell: Я подумал о том, чтобы поставить две сноски об этих двух точках. А потом я подумал, что это не имеет большого значения :)
Ry-
1
@MarcGravel: Я думаю, все думают, что всегда никто не говорит о ситуации «вытащить вилку», потому что программист не может это контролировать :)
Vort3x,
5

Просто чтобы добавить немного к отличному ответу Марка.

Подобные ситуации и есть причина существования lockключевого слова. Это помогает разработчикам убедиться, что блокировка снята в finallyблоке.

Если вы вынуждены использовать Monitor.Enter/, Exitнапример, для поддержки тайм-аута, вы должны обязательно разместить вызов Monitor.Exitв finallyблоке, чтобы обеспечить надлежащее снятие блокировки в случае исключения.

Брайан Расмуссен
источник