Чи залишається заблокований об’єкт заблокованим, якщо всередині нього виникає виняток?


85

Якщо в програмі потокової передачі змінного струму ac # потрібно заблокувати об’єкт, скажімо чергу, і якщо виникне виняток, чи буде об’єкт заблокований? Ось псевдокод:

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

Як я розумію, код після лову не виконується - але мені було цікаво, чи буде звільнено замок.


1
Як остання думка (див. Оновлення) - вам, мабуть, слід утримувати блокування лише на час дії черги ... робити обробку поза замком.
Марк Гравелл

1
Код після catch виконується, оскільки обробляється виняток
cjk

Дякую, мабуть, я це пропустив, чи слід видаляти це питання?
Vort3x

4
Здається, що зразок коду не підходить для цього питання, але питання є досить дійсним.
Сальвадор

Автор: C # Designer - Lock & Exception
Jivan

Відповіді:


88

Спочатку; ви розглядали 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, що може бути проблемою.

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


14
Мені відомий трипарсе, але він не відповідає моєму питанню. Це був простий код для пояснення питання - це не справжня проблема щодо розбору. Будь ласка, замініть синтаксичний аналіз будь-яким кодом, який змусить вас зловити і зробити вас комфортними.
Хададжі

13
А як щодо створення нового винятку ("для ілюстративних цілей"); ;-p
Марк Гравелл

1
За винятком випадків, коли a TheadAbortExceptionвідбувається між Monitor.Enterі try: blogs.msdn.com/ericlippert/archive/2009/03/06/…
Табачник

2
"фатальні катастрофічні невиправні винятки", як перетин потоків.
Філ Купер

98

Я зауважую, що ніхто у своїх відповідях на це старе запитання не згадував, що звільнення блокування за винятком є ​​надзвичайно небезпечною справою. Так, оператори блокування в C # мають семантику "нарешті"; коли елемент керування виходить із блокування нормально або ненормально, замок звільняється. Ви всі говорите про це, ніби це добре, але це погано! Правильно зробити, якщо у вас є заблокована область, яка видає необроблене виняток, - це припинити процес захворювання, безпосередньо перед тим, як він знищить більше даних користувача , а не звільнити блокування і продовжувати рухатися .

Подивіться на це так: припустимо, у вас є ванна кімната із замком на дверях і черга людей, що чекають надворі. У ванній кімнаті вибухнула бомба, внаслідок якої загинула людина. Ваше запитання: "в такому випадку замок буде автоматично розблокований, щоб наступна людина могла потрапити у ванну?" Так це буде. Це не дуже добре. Щойно там вибухнула бомба і когось убила! Водопровід, ймовірно, зруйнований, будинок вже не має міцних конструкцій, і там може бути ще одна бомба . Правильно зробити , як можна швидше вивести всіх і знести весь будинок.

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

Якщо ви заблокували область коду для того, щоб виконати мутацію без одночасного зчитування стану ще одним потоком, і мутація кидає, то якщо дані раніше не були пошкоджені, це впевнено, що зараз . Який саме сценарій, від якого повинен захищати замок . Тепер код, який очікує прочитати цей стан, негайно отримає доступ до пошкодженого стану і, можливо, сам вийде з ладу. Знову ж таки, правильно, що потрібно зробити - це припинити процес.

Як би ви не розрізали його, виняток всередині замку - це погана новина . Правильне запитання - це "чи не зачиститься мій замок у разі виключення?" Правильне запитання - "як мені забезпечити, щоб у замку ніколи не було винятків? А якщо є, то як я можу структурувати свою програму так, щоб мутації відкочувались до попередніх хороших станів?"


18
Ця проблема досить ортогональна блокуванню ІМО. Якщо ви отримаєте очікуваний виняток, ви хочете очистити все, включаючи замки. І якщо ви отримаєте несподіваний виняток, у вас проблема, із замками або без них.
CodesInChaos

10
Я думаю, що ситуація, описана вище, є загальним. Іноді винятки описують катастрофічні події. Іноді вони цього не роблять. Кожен використовує їх по-різному в коді. Цілком справедливо, що виняток є сигналом про виняткову, але не катастрофічну подію - припускаючи, що винятки = катастрофічні, випадок завершення процесу занадто конкретний. Той факт, що це може бути катастрофічною подією, не вилучає з обгрунтованості питання - той самий хід думок може змусити вас ніколи не обробляти жодних винятків, і в цьому випадку процес буде завершено ...
Герасимос Р

1
@GerasimosR: Справді. Два моменти, на які слід наголосити. По-перше, слід вважати винятки катастрофічними, поки не буде визначено доброякісними. По-друге, якщо ви отримуєте доброякісне виняток, викинуте із заблокованої області, то заблокована область, ймовірно, погано розроблена; можливо, він робить занадто багато роботи всередині замку.
Ерік Ліпперт,

1
З усією повагою, але це схоже на те, що ви кажете містеру Ліпперту, що конструкція блокування замків, яка під капотом використовує остаточне твердження C #, погана. Натомість нам слід повністю збити кожну програму, яка коли-небудь мала виняток у операторі блокування, який не потрапив у блокування. Можливо, це не те, про що ви говорите, але якщо так, я дуже радий, що команда пішла по тому шляху, який вона пройшла, а не по вашому (екстремістському з пуристів!) Маршруту. З усією повагою.
Ніколас Петерсен

2
Якщо незрозуміло, що я маю на увазі під некомпонованим: Припустимо, у нас є метод "перенесення", який бере два списки, s і d, блокує s, блокує d, видаляє елемент із s, додає елемент до d, розблокує d, розблокує s. Метод є правильним , якщо ніхто ніколи НЕ намагається передати зі списку X в список Y в той же час , як хто - то ще намагається передати з Y в X . Правильність методу передачі не дозволяє побудувати з нього правильне рішення більшої проблеми, оскільки замки є небезпечними мутаціями глобального стану. Щоб безпечно "перенести", ви повинні знати про кожне блокування програми.
Ерік Ліпперт,

43

так, це випустить належним чином; lockдіє як try/ finally, з Monitor.Exit(myLock)in в нарешті, тому незалежно від того, як ви вийдете, він буде звільнений. Як додаткову нотатку 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 ... konačno. У блоці нарешті викликається Monitor.Exit.

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

Взято з: Цей сайт


1
Існує, принаймні, один випадок, коли це не відповідає дійсності: переривання потоку в режимі налагодження у версіях .net до 4. Причина полягає в тому, що компілятор C # вставляє NOP між Monitor.Enterі try, так що умова "негайно слідує" JIT порушено.
CodesInChaos

6

Ваш замок буде звільнений належним чином. lockДіє таким чином:

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

І finallyблоки гарантовано виконуються, незалежно від того, як ви залишаєте tryблок.


Насправді жоден код не "гарантовано" виконаний (наприклад, ви можете потягнути за силовий кабель), і це не зовсім так, як виглядає замок у 4.0 - див. Тут
Marc Gravell

1
@MarcGravell: Я думав про те, щоб додати дві примітки саме про ці два моменти. І тоді я зрозумів, що це не матиме великого значення :)
Ry-

1
@MarcGravel: Я думаю, кожен припускає, що завжди не йдеться про ситуацію "витягни штепсель", оскільки це не те, що програміст може контролювати :)
Vort3x

5

Тільки щоб додати трохи до чудової відповіді Марка.

Такі ситуації є самою причиною існування lockключового слова. Це допомагає розробникам переконатися, що блокування звільнено вfinally блоці.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.