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

时间:2009-02-26 11:31:14

标签: c# .net multithreading exception locking

在c#线程应用程序中,如果我要锁定一个对象,让我们说一个队列,如果发生异常,该对象是否会保持锁定状态?这是伪代码:

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

据我所知,catch之后的代码没有执行 - 但我一直想知道锁是否会被释放。

6 个答案:

答案 0 :(得分:92)

我注意到在这个旧问题的答案中没有人提到在异常时释放锁定是一件非常危险的事情。是的,C#中的锁定语句具有“最终”语义;当控制器正常或​​异常退出锁定时,锁定被释放。你们都在谈论这件事,好像这是一件好事,但这是一件坏事!如果你有一个引发未处理异常的锁定区域,那么正确的做法是在它破坏更多用户数据之前立即终止病态进程,而不是释放锁定并继续进行

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

我的意思是,通过考虑:如果你锁定了一个代码区域,以便从数据结构中读取而不在另一个线程上进行变异,并且该数据结构中的某些东西引发了异常,那么几率很高这是因为数据结构已损坏。用户数据现在搞砸了;此时您不想尝试保存用户数据,因为您正在保存损坏的数据。只需终止该过程。

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

无论你如何切片,锁内的异常都是坏消息。要问的正确问题不是“如果发生异常,我的锁会被清除吗?”要问的正确问题是“如何确保锁定内部永远不会出现异常?如果有,那么我如何构建我的程序以使突变回滚到以前的良好状态?”

答案 1 :(得分:80)

首先;你考虑过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的持续时间内持有锁,这可能是个问题。

所以它将在除了最致命的灾难性不可恢复的例外之外发布。

答案 2 :(得分:37)

是的,这将正确发布; lock充当try / finally,其中Monitor.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);
    }
} 

答案 3 :(得分:13)

“锁定语句被编译为对Monitor.Enter的调用,然后是try ... finally块。在finally块中,调用Monitor.Exit。

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

取自: This site

答案 4 :(得分:5)

您的锁将被正确释放。 lock的行为如下:

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

无论您如何离开finally块,都可以保证try块执行。

答案 5 :(得分:4)

只是为Marc的优秀答案添加一点。

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

如果您被迫使用Monitor.Enter / Exit,要支持超时,您必须确保将Monitor.Exit块中的finally调用放在{{1}}块中,以确保在发生异常时正确释放锁定。