Останется ли заблокированный объект заблокированным, если внутри него возникнет исключение? - PullRequest
74 голосов
/ 26 февраля 2009

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

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

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

Ответы [ 6 ]

92 голосов
/ 27 февраля 2012

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

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

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

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

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

81 голосов
/ 26 февраля 2009

Во-первых; Вы рассматривали 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, что может быть проблемой.

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

37 голосов
/ 27 февраля 2012

да, это выпустит правильно; lock действует как try / finally, с 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);
    }
} 
13 голосов
/ 01 октября 2009

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

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

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

5 голосов
/ 27 февраля 2012

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

try {
    Monitor.Enter(myLock);
    // ...
} finally {
    Monitor.Exit(myLock);
}
Блоки

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

4 голосов
/ 26 февраля 2009

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

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

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

...