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

Kha*_*aji 77 .net c# multithreading locking exception

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

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

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

Eri*_*ert 93

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

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

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

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

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

  • 这个问题与锁定IMO非常正交.如果您收到预期的异常,则需要清理所有内容,包括锁定.如果您遇到意外的异常,则会出现问题,无论是否有锁. (14认同)
  • 我认为上述情况是一般性的.有时,例外描述了灾难性事件.有时候他们没有.每个人在代码中使用它们的方式不同.异常是异常但非灾难性事件的信号是完全有效的 - 假设异常=灾难性,过程终止案例过于具体.可能是灾难性事件的事实并没有从问题的有效性中消除 - 同样的思路会导致你永远不会处理任何异常,在这种情况下,该过程将被退出...... (8认同)
  • @NicholasPetersen:首先,是的,我*确实*害怕并且厌恶锁!:-) 编程是*将小问题的正确解决方案组合成大问题的正确解决方案*但是*包含锁的代码是不可组合的*。锁积极地“对抗”语言的可用特性!现在,也就是说,如果我们要在同一种语言中使用锁和异常,并且如果 lock 语句是 try-finally 的糖衣,那么是的,我非常喜欢你创建 catch 块的想法。好主意! (2认同)
  • 如果我不清楚我的意思是不可组合:假设我们有一个方法"转移",它带有两个列表,s和d,锁定s,锁定d,从s中删除一个项目,将项目添加到d,解锁d,解锁.该方法仅正确*如果没有人试图在其他人试图从Y转移到X*的同时从列表X转移到列表Y. 传输方法的正确性不允许您从中构建更大问题的正确解决方案,因为*锁是全局状态的不安全突变.*要安全地"转移",您必须知道程序中的*每个锁*. (2认同)

Mar*_*ell 83

第一; 你考虑过TryParse吗?

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

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

Monitor.Enter(lockObj);
try {
  // ...
} finally {
    Monitor.Exit(lockObj);
}
Run Code Online (Sandbox Code Playgroud)

第二; 你捕获并且不会重新抛出内部异常,所以lock从来没有真正看到异常.当然,您在MessageBox的持续时间内持有锁,这可能是个问题.

因此它将在除了最致命的灾难性不可恢复的例外之外发布.

  • 我知道tryparse,但它与我的问题并不真正相关.这是解释问题的简单代码 - 不是对解析的真正关注.请用*any*代码替换解析,这将强制捕获并让您感到舒适. (12认同)
  • 如何抛出新的异常("用于说明目的"); ;-p (12认同)
  • 除非在“Monitor.Enter”和“try”之间发生“TheadAbortException”:http://blogs.msdn.com/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix .aspx (2认同)
  • “致命的灾难性不可恢复的异常”,例如穿越河流。 (2认同)

Mar*_*ell 41

是的,这将正确释放; 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);
    }
} 
Run Code Online (Sandbox Code Playgroud)


GH.*_*GH. 13

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

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

摘自: 本网站


Bri*_*sen 6

只是为 Marc 的出色答案添加一点。

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

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


Ry-*_*Ry- 5

你的锁将被正确释放.这样的lock行为:

try {
    Monitor.Enter(myLock);
    // ...
} finally {
    Monitor.Exit(myLock);
}
Run Code Online (Sandbox Code Playgroud)

finally块保证你如何离开来执行,不管try块.