【问题标题】:In C# how can I safely exit a lock with a try catch block inside?在 C# 中,如何安全地退出带有 try catch 块的锁?
【发布时间】:2009-03-12 16:47:07
【问题描述】:

这是一个发生在锁内的异常示例,带有一个 try-catch 块。

int zero = 0;
int j = 10;

lock (sharedResource.SyncRoot)
{
    try
    {
        j = j / zero;
    }
    catch (DivideByZeroException e)
    {
        // exception caught but lock not released
    }
}

如何安全地释放这个锁?

【问题讨论】:

  • 为证明这一点而创建 IL 的旁注:由于 div/zero 错误,上述代码甚至无法编译 :)

标签: c#


【解决方案1】:

不会自动释放吗?

来自 MSDN 锁的意思

System.Threading.Monitor.Enter(x);
try {
   ...
}
finally {
   System.Threading.Monitor.Exit(x);
}

所以你不必费心。

【讨论】:

  • 确实,这似乎是正确的。您实际上已经嵌套了 try-finally 块,这意味着无论发生什么,都会调用两个 finally 块both
  • +1,这是您可以使用 CLR 2.0 做的最好的事情,但它实际上并不是万无一失的。 blogs.msdn.com/ericlippert/archive/2009/03/06/…
  • 更新了 Eric 文章的链接 - ericlippert.com/2009/03/06/locks-and-exceptions-do-not-mix。请注意,虽然锁本身不再泄漏,但由于lock 的异常而失败意味着状态更新可能完成了一半,从而破坏了锁的目的。在此答案中建议“不要打扰”之前,请仔细阅读链接文章。
【解决方案2】:

在您超出 lock(sharedResource.SyncRoot) 块的范围之前,不会释放锁。 lock (sharedResource.SyncRoot) {} 基本等同于:

Monitor.Enter(sharedResource.SyncRoot);
try
{
}
finally
{
    Monitor.Exit(sharedResource.SyncRoot);
}

如果您想要更多控制权,您可以自己执行 Enter/Exit,或者只是将锁定范围重新调整为您想要的,例如:

try
{
    lock(sharedResource.SyncRoot)
    {
        int bad = 2 / 0;
    }
}
catch (DivideByZeroException e)
{
   // Lock released by this point.
}

【讨论】:

  • 第一个例子很好,如果你持有的资源是一个数据库,你希望能够在Catch中向数据库报告错误,关闭Db然后释放锁。
【解决方案3】:

证明。

.method public hidebysig instance void  test(int32 i) cil managed
{
  // Code size       43 (0x2b)
  .maxstack  2
  .locals init ([0] int32 bad,
           [1] class [mscorlib]System.DivideByZeroException e,
           [2] object CS$2$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldfld      object WebApplication1.myclass::mutex
  IL_0007:  dup
  IL_0008:  stloc.2
  IL_0009:  call       void [mscorlib]System.Threading.Monitor::Enter(object)
  IL_000e:  nop
  .try
  {
    IL_000f:  nop
    .try
    {
      IL_0010:  nop
      IL_0011:  ldc.i4.2
      IL_0012:  ldarg.1
      IL_0013:  div
      IL_0014:  stloc.0
      IL_0015:  nop
      IL_0016:  leave.s    IL_001d
    }  // end .try
    catch [mscorlib]System.DivideByZeroException 
    {
      IL_0018:  stloc.1
      IL_0019:  nop
      IL_001a:  nop
      IL_001b:  leave.s    IL_001d
    }  // end handler
    IL_001d:  nop
    IL_001e:  nop
    IL_001f:  leave.s    IL_0029
  }  // end .try
  finally
  {
    IL_0021:  ldloc.2
    IL_0022:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
    IL_0027:  nop
    IL_0028:  endfinally
  }  // end handler
  IL_0029:  nop
  IL_002a:  ret
} // end of method myclass::test

【讨论】:

    【解决方案4】:

    Jaredpar 在评论中发布了一个我认为值得一试的链接:

    http://blogs.msdn.com/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx

    在这篇博文中,Eric Lippert cmets 讨论了与 C# 中的锁定相关的问题:

    这里的问题是,如果 编译器生成无操作指令 在显示器输入和 尝试保护区域然后它是 运行时可能抛出一个 之后的线程中止异常 监视器进入但在尝试之前。在 那个场景,最后永远不会运行 所以锁泄漏,可能最终 使程序死锁。这将是 如果这是不可能的,那就太好了 未优化和优化的构建。

    【讨论】:

      【解决方案5】:

      您的代码非常好。 lock(sth){...} 在内部转换为 try finally 块。

      【讨论】:

        【解决方案6】:

        当它的块的上下文退出时,锁将被释放,然而这是发生的。在上面给出的代码示例中,当控制退出最终的 } 上下文时,锁将自动、安全地释放。

        【讨论】:

          【解决方案7】:

          不管它是不是就这样运行:

          try
          {
            lock (sharedResource.SyncRoot)
            {
                int bad = 2 / 0;
            }
          }
          catch (DivideByZeroException e)
          {
              // exception caught but lock not released
          }
          finally
          {
                //release lock
          }
          

          【讨论】:

          • 你会用什么来代替 //release lock ?
          • 表示不做任何事情就会释放锁
          • 锁在你所处的时候被释放 // 异常被捕获但锁没有被释放
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-05-27
          • 1970-01-01
          • 2013-07-07
          • 1970-01-01
          • 2013-04-06
          • 2011-03-22
          相关资源
          最近更新 更多