【问题标题】:Monitor.Exit() sometimes hangsMonitor.Exit() 有时会挂起
【发布时间】:2016-04-19 07:10:50
【问题描述】:

我在使用 Monitor.Enter 和 Monitor.Exit 时似乎遇到了并发问题。有时我的代码会挂在下面的 Monitor.Exit 语句上:

public void EndInit()
{
    Monitor.Enter(this.lockObj);
    this.initCount--;
    if (this.initCount == 0) {
        this.IsInitializing = false;
        this.IsInitialized = true;
        this.OnInitialized();
    }
    // sometimes, this Exit will never return ...
    Monitor.Exit(this.lockObj);
}

只有另外一个地方使用了我的 lockObj

public void BeginInit()
{
    Monitor.Enter(this.lockObj);
    this.initCount++;
    this.IsInitializing = true;
    this.IsInitialized = false;
    Monitor.Exit(this.lockObj);
}

这就是我声明这个同步对象的方式:

private readonly object lockObj = new object();

我竭尽全力想知道这里发生了什么,但没有成功。我希望Monitor.Enter() 会阻止直到我的同步对象被释放,但为什么Monitor.Exit() 会被阻止?我在 MSDN 中也找不到对这种行为的任何解释。

注意我无法重现这种行为,它是随机发生的(好吧,我知道“随机”不是正确的措辞)。

非常感谢任何想法或有用的提示!

索斯滕

【问题讨论】:

  • 如果可能,您可以将 Monitor.Enter / Monitor.Exit 组合替换为 lock() { } 块。如果在Monitor.Exit 调用之前抛出异常会发生什么?

标签: c# multithreading locking


【解决方案1】:

我正在根据我之前的评论做出回答。因为您应该使用try finally 构造来正确地在OnInitialize() 中发生异常时调用Monitor.Exit

所以代码会变成

public void EndInit()
{
    Monitor.Enter(this.lockObj);
    try
    {
        this.initCount--;
        if (this.initCount == 0) {
            this.IsInitializing = false;
            this.IsInitialized = true;
            this.OnInitialized();
        }
    } 
    finally
    {
        Monitor.Exit(this.lockObj);    
    }
}

第二种方法也是如此。

那么也可以这样写

public void EndInit()
{
    lock(this.lockObj)
    {
        this.initCount--;
        if (this.initCount == 0) {
            this.IsInitializing = false;
            this.IsInitialized = true;
            this.OnInitialized();
        }
    }
}

编辑

可以在here 找到由 Joe Albahari 编写的关于线程的非常好的解释。值得一读。

编辑 2(为了完整性)

正如 Damien_The_Unbeliever 所说,还有一个 overload

这仅适用于 .NET 4 及更高版本。使用监视器的代码将变为:

public void EndInit()
{
    bool lockAcuired = false;
    try
    {
        Monitor.Enter(this.lockObj, ref lockAquired);
        this.initCount--;
        if (this.initCount == 0) {
            this.IsInitializing = false;
            this.IsInitialized = true;
            this.OnInitialized();
        }
    } 
    finally
    {
        if(lockAquired)
            Monitor.Exit(this.lockObj);    
    }
}

【讨论】:

  • 如果你要手动实现lock,你应该使用try块内使用两个参数Enter的变体enclosed。它是由 .NET 4(我相信)引入的,用于处理在 Enter 已返回和 try 块开始之间引发异常的边缘情况(在这种情况下,Exit 不会被调用,在您当前变体)
  • 在编写这个问题并深入研究我自己的(旧)代码时,我只是想出了这个问题的相同(潜在)来源:OnInitialized() 可能会抛出一个当前未处理的异常(并且甚至无法识别)。我将此调用移出同步块并添加了 try / catch 。未来会显示,如果这可以解决问题。
  • @Damien_The_Unbeliever 我更新了 .NET4 版本的答案。感谢您指出这一点。
  • 可能为我节省了很多时间,而这些时间会浪费在寻找最简单的工作解决方案上。谢谢。
猜你喜欢
  • 2017-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-07
  • 1970-01-01
  • 1970-01-01
  • 2015-06-02
  • 2020-03-13
相关资源
最近更新 更多