我可以丢弃锁定的物体吗?

时间:2013-09-30 12:19:19

标签: c# multithreading

如果我在锁定的对象上调用Dispose()会怎样?

lock (obj)
{
    obj.Dispose();
}

如果我在这种情况下跳过Monitor.Exit()电话会怎样?

Monitor.Enter(obj);
obj.Dispose();

5 个答案:

答案 0 :(得分:6)

Dispose的实现通常不是线程安全的。

这是因为在仍有可能使用的引用时,不应丢弃对象。这意味着你不应该处理任何其他线程持有引用的对象。

IDisposable / Dispose不是管理对象生命周期的方法,无论是一般还是跨线程 - 它是释放对象资源的范例它的生命周期结束了。 (using语句是一个习惯用法/范例,用于对象的适当生命周期是语句的范围。)

因此,如果您在另一个线程已锁定对象时调用dispose,则某些内容已经非常错误。

答案 1 :(得分:5)

  

如果我在锁定对象上调用Dispose()会怎样?

首先,对象本身并未锁定(受保护)。 lock关键字中使用的引用用于标记或标记代码的一部分,该部分不应与使用相同对象引用的任何其他(或相同)代码段同时运行。它实际上并不影响对象本身。这是关于锁如何在.NET中工作的一种非常常见的误解。

在此上下文中,调用Dispose与调用任何其他方法没有什么不同。没有什么特别的事情发生。它只是意味着两个不同的线程不能同时执行Dispose。你所做的不仅是可以接受的,而且如果这个类不是线程安全的,那么实际上是推荐的。

  

如果我在这种情况下跳过Monitor.Exit()调用会发生什么:

你应该总是释放锁。请记住,锁不会对对象引用本身起作用,因此应该更容易理解您将在获取状态中保持锁定状态。物体被丢弃并不重要。请记住,Monitor(或lock)不会锁定或保护对象本身。它只标记或标记一段代码。如果某个线程再次尝试获取具有相同对象的锁,则该线程将被强制无限期地等待,从而可能导致死锁。

一个更有趣的问题是,这是否会导致内存泄漏。 Monitor.Enter根对象吗?答案是不。这可以通过以下示例进行演示。

public class Program
{
    public static void Main(string[] args)
    {
        var foo = new Foo();
        Monitor.Enter(foo);
        foo = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.ReadLine();
    }
}

internal class Foo
{
    ~Foo()
    {
        Console.WriteLine("~Foo");
    }
}

如果你编译并运行它,你会发现打印出“~Foo”。这表明Monitor.Enter内部没有引用。因此,虽然不建议跳过Monitor.Exit调用,但在知道它不会导致内存泄漏时,您可以放心。 1


1 实际上,这可能不完全正确。虽然很容易证明没有托管内存泄漏,但在非托管领域可能会有所不同。如果没有查看SSCLI代码,我们真的不知道Monitor.Enter在内部做了什么。也许它在非托管堆或堆栈中分配一个额外的数组槽(或其他)。我想微软认为这个模糊的场景,但谁知道呢。关键是Monitor.EnterMonitor.Exit来电应始终配对,这样您就不用担心了。

答案 2 :(得分:4)

Dispose范例与lock范例不同。您可以安全地处置当前持有互斥锁的对象;但是,你之后仍然应该释放锁。如果你没有这样做,那么在(现在处置的)对象上调用Monitor.Enter的其他线程将无限期地阻塞。

答案 3 :(得分:1)

对于你的第一个问题 - 发生的事情是在任何给定时刻只有一个威胁可能会调用Dispose()。 对于第二个问题 - 如果多个线程运行此代码,则第一个问题将调用Dispose方法。其余的将永远阻止。

dispose方法中有 nothing 特殊。它就像任何其他方法一样,除了它参与一些语法糖(例如,使用语句)。

答案 4 :(得分:1)

您始终可以锁定现有对象,Dispose方法无法删除您的对象,因此该对象的引用仍然存在。 Dispose()方法不会影响对象的实例。