C#如何检测对象是否已被锁定

49

我该如何检测对象是否已被锁定?

Monitor.TryEnter(在这里有描述)不能用于我的情况,因为如果对象未被锁定,则会锁定该对象。

想检查对象是否锁定,而在代码的其他地方我将使用Monitor类来锁定该对象。

我知道可以使用布尔字段(例如 private bool ObjectIsLocked),但我想使用锁定对象本身来检测它。

下面的示例代码显示了我的需求:

private static object myLockObject = new object();

private void SampleMethod()
{
    if(myLockObject /*is not locked*/) // First check without locking it
    {
        ...
        // The object will be locked some later in the code
        if(!Monitor.TryEnter(myLockObject)) return;

        try
        {

            ....
        }
        catch(){...}
        finally
        {
            Monitor.Exit(myLockObject);
        }
    }
}

1
如果在 if(myLockObject /*is not locked*/) 后立即被其他线程锁定,那么这不会成为一个问题吗? - default
4
请仔细思考这一点——一旦“is this object locked”方法返回结果,答案就可能会发生变化——除非在测试中,获取了锁。此时,你也只知道答案“它被我锁定了”,直到你释放它为止。因此,独立的“is this object locked”方法可能始终应该硬编码为返回false(或true,你可以选择)。 - Damien_The_Unbeliever
@Damien_The_Unbeliever 我也是这么想的,虽然你表达得更清楚 :) - default
@Damien_The_Unbeliever(我不确定我是否正确理解了你的答案,但是)Pete的答案表明,在.NET 4.5中添加了Monitor.IsEntered,这表明可以检测对象是否已锁定。此外,如果对象被锁定,其他线程肯定可以检测到,否则锁定架构根本无法工作。如果其他线程无法检测对象是否已锁定,则会忽略锁定。 - hwcverwe
非阻塞锁是跳过队列的替代方案。 - Tyler S. Loeper
显示剩余6条评论
6个回答

58
你做错了。如果你没有对象上的锁,你就无法检查它是否被锁定(而如果你拥有锁,你将提前知道)。你可以“询问”“是否被锁定?”并得到一个“否”的回答,然后在下一纳秒,另一个线程可以获取锁,导致你的程序处于损坏状态。这绝对不是多线程应用程序的正确方式,也是为什么.NET没有一个Monitor.IsLocked方法的原因。如果你的代码在获取锁之前需要检查锁的状态,那么你的设计有问题。试图用未保护的标志来解决这个问题是一个不好的解决方案,百分之百保证不会起作用。
无论如何,不要使用一个bool变量来表示多线程的“是否被锁定”状态(因为你可能会遇到同样的问题,你读取到“false”,然后1纳秒后另一个线程会将其写为“true”)。使用Interlock.CompareExchange来解决这个问题。
private static int _lockFlag = 0; // 0 - free

if (Interlocked.CompareExchange(ref _lockFlag, 1, 0) == 0){
   // only 1 thread will enter here without locking the object/put the
   // other threads to sleep.

   Monitor.Enter(yourLockObject); 

   // free the lock.
   Interlocked.Decrement(ref _lockFlag);
}

你会发现你需要在每个可能获取对象锁的地方改变_lockFlag。换句话说,你将在原生锁系统周围构建一个自定义的锁系统。

3
针对这种情况,你有什么建议?我正在制作一个文件管理器。由于构建非常复杂,我正在使用多线程获取自定义的FileItems。我希望尽可能地获取和构造这些FileItems,但同时也想尽可能快地响应前端用户界面。因此,如果更新UI的线程被锁定,就继续获取FileItems。如果没有锁定,则在当前未完成列表上发出信号。这样一来,处理不会依赖锁。 - Tom Padilla
@TomPadilla,确切的解决方案将取决于底层UI框架。一种解决方案是:a)创建一个服务类来保存FileItems加载。该服务必须提供同步的Add / Remove方法(以及您需要查询服务内部FileItems集合的方法)。然后,您可以构建一个多线程的FileItems加载器。每当它加载新的FileItem时,它必须调用其他服务的Add方法。最后,您的服务必须提供一个OnUpdate事件。然后,UI线程订阅此事件并采取(或安排)必要的步骤来处理数据... - Marcelo De Zen
1
同意。如果你遇到了这个问题,创建一个“IsLocked”标记是代码异味,并且会导致随着用户操作不同而出现的难以重现的随机错误。 - rollsch
@rolls 我也不确定。在任何多线程(或UWP多任务)应用程序中,错误的代码会创建无法重现的bug。但是我可以看到一个任务决定“接下来该做什么”。如果我选择(A),我将遇到锁定问题,但(B)没有问题。我同意标志可以解决这个问题,但这也会带来麻烦。您可以编写自己的bool Lock("一些名称",Action),这样您就可以编码if (Lock("报告",()=>Report(this))并且布尔值表示是否完成。 - Paulustrious
@MarceloDeZen - 如果只有一个线程可以进入if块,为什么要使用Interlocked.Decrement(ref _lockFlag)?代码是否可以安全地说_lockFlag = 0? - Jeff Maass
显示剩余7条评论

12

使用C#中的Monitor类无法实现此操作。

只需使用:

    var lockedBySomeoneElse = !Monitor.TryEnter(obj);
    if (!lockedBySomeoneElse) Monitor.Exit(obj);
    // the variable 'lockedBySomeoneElse' has the info you want

其他锁,如readerwriterlockslim并不能真正帮助。它可以告诉你有多少个读者,但无法告诉你是否有一个写入者正在忙碌 ;-(

如果您使用自己的建议“private bool ObjectIsLocked”,这也是我会采取的方法,您应该使用

      private volatile bool ObjectIsLocked

这将使C#更好地反射它的多线程更新。


1
由于锁是可重入的,您可能希望首先检查Monitor.IsEntered,以防此操作用作断言以验证该锁是否由任何人持有。即使锁已经被占用,只要它是由同一线程调用的,TryEnter就会成功。 - jackmott

11

Monitor.IsEntered 可以解决这个问题。

编辑:我刚刚重新阅读了文档,它说:

确定当前线程是否持有指定对象的锁。

所以这还不够,因为你可能想知道不同的线程是否持有锁?


1
根据文档,那似乎只针对当前线程,是吗? - default
1
很好。问题是...我们正在使用4.0版本。(我会修改我的问题并添加这个限制) - hwcverwe
回答你的问题,是的,我想知道不同的线程是否持有该锁。 - hwcverwe

0
如果您想确保将来仍然可以锁定对象,只需调用 TryEnter 并在整个时间内保持锁定状态。否则,如果您想稍后尝试锁定对象,只需调用 TryEnter,并在对象被锁定时立即解锁。

0

从技术上讲,您可以检查对象的同步块索引字段,该字段具有与同步块数组中关联的惰性分配结构的索引 - 每个对象都具有此字段,并且每个用于同步的对象都设置了此字段。这些结构用于协调线程同步。但是,我非常怀疑您能否在没有分析 API的情况下访问此信息。


-3
我绝不赞成检查锁定然后进入代码块。然而,当我正在寻找一种方法来检查新函数不能使对象保持锁定时,我发现了这个线程。基于 Monitor.IsEntered 的单元测试正好给了我想要的结果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接