【问题标题】:Monitor.TryEnter / Monitor.Exit and SynchronizationLockExceptionMonitor.TryEnter / Monitor.Exit 和 SynchronizationLockException
【发布时间】:2012-12-30 09:22:04
【问题描述】:

是否可以检测是否是同一个线程试图释放锁? 我们在代码中有很多地方看起来像:

try
{
    try
    {
       if(!Monitor.TryEnter(obj, 2000)) 
       { 
            throw new Exception("can not lock"); 
       }
    }
    finally
    {
       Monitor.Exit(obj);
    }
}
catch
{
    //Log
}

上面的代码非常简化,实际上Enter和Exit语句位于自定义对象(锁管理器)中。

问题是,在那个结构中,我们在尝试“退出”时有SynchronizationLockException,因为它看起来像没有成功锁定的线程,最终试图释放。

所以问题是,我如何知道制作 Monitor.Exit 的线程是否与执行 Monitor.Enter 的线程相同?
我认为我可以使用 CurrentThread.Id 来同步进入和退出,但我不确定它是否足够“安全”。

【问题讨论】:

  • 既然您声明实际的 Enter 和 Exit 语句位于自定义对象中,我想知道您是否可以访问外部块中的obj
  • 不,在外部块中我没有对对象的引用,因为锁管理器接收“key”作为参数并创建它自己的锁对象。您可以在我之前的问题中看到我的锁管理器:stackoverflow.com/questions/14030491/…

标签: c# .net multithreading locking


【解决方案1】:

所以问题是,我如何知道创建 Monitor.Exit 的线程是否与创建 Monitor.Enter 的线程相同?

据我所知,你不能轻易做到。您无法找出哪个线程拥有监视器。

然而,这只是一个编码问题——你应该改变你的代码,这样它甚至不会尝试在不应该释放监视器的时候。所以你上面的代码可以重写为:

if (!Monitor.TryEnter(obj, 2000))
{
    throw new Exception(...);
}
try
{
    // Presumably other code
}
finally
{
     Monitor.Exit(obj);
}

或者更好的是,如果您使用的是 .NET 4,请使用接受 ret 参数的 overload of TryEnter

bool gotMonitor = false;
try
{
    Monitor.TryEnter(obj, ref gotMonitor);
    if (!gotMonitor)
    {
        throw new Exception(...);
    }
    // Presumably other code
}
finally
{
    if (gotMonitor)
    {
        Monitor.Exit(obj);
    }
}

【讨论】:

  • 根据帖子,内部try-finally似乎没有打算修改。
  • @KenKin,是的,try-catch 不应该被修改,但看起来在当前代码结构中无法实现我需要的。我考虑了一下,但仍然希望解决它:) Jon,感谢您的明确回答,现在我们需要决定是否删除使用 TryEnter 并改用 Enter,或者我们需要重构大量代码。
  • @KenKin:鉴于它损坏了,我认为它基本上必须修改它是合理的......
  • @AlexDn:从根本上说,您的代码已损坏。我强烈建议修复它而不是尝试解决它。目前尚不清楚为什么需要付出很多努力 - 你在很多地方都有这种模式吗?如果是这样,首先要重构的是 - 看看您是否可以找到一种在单个位置表示控制流的方法(例如,如果可以获取,则传入一个委托以在锁内执行)。
  • @AlexDn:我仍然说您基本上是在解决损坏的代码。这就像捕获 NullReferenceException 而不是修复代码以避免取消引用 null 值 - 是的,它有效,但最好是“修复”代码而不是在上面贴膏药。
【解决方案2】:

您认为在 try-catch 中调用 Monitor.Exit 是 'durty'(脏?),这里有一个非常简单的想法,试图“带走 durty”。锁对于同一个线程是可重入的,如果一个线程成功获取,在它释放之前,另一个线程的尝试将失败。这样你就可以考虑这样的事情:

public void Exit(object key) {
    if(!IsActive) {
        return;
    }

    if(LockDictionary.ContainsKey(key)) {
        var syncObject=LockDictionary[key];

        if(Monitor.TryEnter(syncObject.SyncObject, 0)) {
            SetLockExit(syncObject);
            Monitor.Exit(syncObject.SyncObject);
            Monitor.Exit(syncObject.SyncObject);
        }
    }
}

我们调用 Monitor.Exit 两次,因为我们将它锁定了两次,一次在代码外部,一次在此处。

【讨论】:

  • 嗯...非常好的“干净”解决方案:) 它看起来很简单,并且应该适用于我的情况!非常感谢你!顺便说一句,'durty' 是 'dirty' - 对不起我丑陋的英语:))
  • 如果LockDictionary 在这里是Dictionary<,>,你需要注意它不是线程安全的。我仍然认为修复代码而不是解决它的缺陷会是一个更好的主意。
  • @Ken Kin,您能解释一下,为什么第一次 TryEnter 失败的线程无法第二次进入锁?在开始执行 finally 块的时候,另一个线程已经可以离开它了。
  • @mistika:等价码错误,我已经删除了。
【解决方案3】:

我知道这是一个较老的问题,但无论如何这是我的答案。 我会在 if 中移动 try-finally 构造:

try
{
    if(Monitor.TryEnter(obj, 2000))
    {
        try
        {
            // code here
        }
        finally
        {
            Monitor.Exit(obj);
        }
    }
    else
    {
        throw new Exception("Can't acquire lock");
    }
}
catch
{
    // log
}

【讨论】:

    猜你喜欢
    • 2014-12-18
    • 1970-01-01
    • 2014-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-23
    相关资源
    最近更新 更多