如果内部发生异常,锁定的对象是否保持锁定状态?


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
捕获后的代码确实会执行,因为处理了异常
cjk

谢谢,我一定错过了那个问题,我应该删除这个问题吗?
Vort3x 2012年

4
样本代码似乎不适用于此问题,但是该问题非常有效。
SalvadorGomez

由C#Designer- Lock&Exception
Jivan

Answers:


88

第一; 您是否考虑过TryParse?

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

锁定将被释放的原因有两个:首先,lock本质上是:

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

第二; 您会捕获并且不会重新抛出内部异常,因此lock从不实际看到异常。当然,您在整个MessageBox期间都持有该锁,这可能是一个问题。

因此,它将在除了最致命的灾难性不可恢复的异常之外的所有异常中释放。


14
我知道tryparse,但这与我的问题并不真正相关。这是用于解释问题的简单代码-并不是对解析的真正关注。请使用任何会强制捕获并使您感到舒适的代码替换该解析。
卡达吉2009年

13
如何抛出新的Exception(“出于说明目的”);;-p
Marc Gravell

1
除非一个TheadAbortException的之间发生Monitor.Entertryblogs.msdn.com/ericlippert/archive/2009/03/06/...
有约色塔巴奇尼克

2
“致命的灾难性不可恢复的例外”,例如越过溪流。
Phil Cooper

98

我注意到,没有人在他们对这个老问题的回答中提到,对异常解除锁定是一件非常危险的事情。是的,C#中的lock语句具有“ finally”语义。当控件正常或异常退出锁时,将释放锁。你们都在说这是一件好事,但这是一件坏事!如果您有一个引发未处理异常的锁定区域,那么正确的做法是在终止有病的进程之前立即终止该进程,该进程会破坏更多的用户数据,而不是释放锁定并继续进行下去

这样看:假设您有一间浴室,门上有锁,外面有一排人。浴室里的炸弹爆炸,杀死在那里的人。您的问题是“在那种情况下,锁会自动解锁,以便下一个人进入浴室吗?” 是的,它会的。 那不是一件好事。一枚炸弹刚炸进去,炸死了一个人!管道可能已被破坏,房屋的结构已不再健全,那里可能还有炸弹。正确的做法是尽快让所有人离开,并拆除整个房屋。

我的意思是,仔细考虑一下:如果您锁定了代码区域以便从数据结构中读取数据而不在另一个线程上对其进行突变,并且该数据结构中的某些内容引发了异常,则很可能是因为该数据结构腐败了。用户数据现在混乱了;您此时不想尝试保存用户数据,因为那样您将保存损坏的数据。只需终止该过程即可。

如果您锁定了代码区域以便执行变异而又没有另一个线程同时读取状态,并且抛出了变异,那么如果数据以前没有损坏,那么现在肯定是。这正是锁应该防止的情况。现在,等待读取该状态的代码将立即被授予访问损坏状态的权限,并且可能自身崩溃。同样,正确的做法是终止该过程。

无论如何切片,锁中的异常都是一个坏消息。正确的问题不是“如果发生异常,我的锁会被清理吗?” 正确的问题是:“如何确保锁中永远不存在异常?如果存在,那么如何构造程序,以便使突变回滚到以前的良好状态?”


18
这个问题与锁定IMO非常正交。如果收到预期的异常,则要清除所有内容,包括锁。而且,如果遇到意外的异常,则可能会出现问题,无论是否带有锁。
CodesInChaos

10
我认为上述情况是普遍的。有时,异常描述灾难性事件。有时他们没有。每个人在代码中使用它们的方式都不同。异常是异常但非灾难性事件的信号是完全正确的-假设异常=灾难性,过程终止的情况过于具体。可能是灾难性事件的事实并不能从问题的有效性中消除-相同的思路可能会导致您从不处理任何异常,在这种情况下,该过程将退出...
Gerasimos R

1
@ GerasimosR:的确如此。有两点要强调。首先,在确定为良性之前,应假定例外是灾难性的。其次,如果您从锁定区域抛出良性异常,则锁定区域可能设计不良;它可能在锁内做了太多工作。
埃里克·利珀特

1
所有应有的尊重,但听起来您像是在说Lippert先生,引擎盖下采用final语句的C#设计是一件坏事。取而代之的是,我们应该彻底崩溃每个未在锁内捕获的具有异常的锁的应用。也许那不是您在说的,但是如果是这样,我真的很高兴团队走他们所走的路线,而不是您的路线(出于纯粹主义者的原因,是极端主义者!)。所有应有的尊重。
尼古拉斯·彼得森

2
如果不清楚“非可组合”的含义:假设我们有一个“转移”方法,该方法接受两个列表,s和d,锁s,锁d,从s中删除一个项目,将该项目添加到d,解锁d,解锁s。仅当没有人尝试同时从其他人​​尝试从Y到X转移到列表Y时,该方法才是正确的。由于锁是全局状态的不安全突变,因此转移方法的正确性不允许您为较大的问题构建正确的解决方案为了安全地“转移”,您必须了解程序中的每个锁
埃里克·利珀特

43

是的,那可以正常释放;lock用作try/ finallyMonitor.Exit(myLock)最后一个带有/ ,因此无论您如何退出它都将被释放。附带说明一下,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。

x86和x64的JIT代码生成确保在Monitor.Enter调用和紧随其后的try块之间不会发生线程中止。”

来自: 本网站


1
至少有一种情况是不正确的:在4之前的.net版本上,调试模式下的线程中止。原因是C#编译器在Monitor.Enter和之间插入了一个NOP 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 2012年

5

只是为Marc的出色回答增加了一点。

出现这种情况是存在lock关键字的原因。它可以帮助开发人员确保锁已在finally块中释放。

如果您被迫使用Monitor.Enter/Exit例如支持超时,则必须确保将调用放置Monitor.Exitfinally块中,以确保在发生异常的情况下正确释放锁。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.