使用析构函数的IDisposable:需要线程安全的实现吗?

时间:2011-01-17 08:36:03

标签: c# .net thread-safety dispose finalizer

这几乎只有我确保,我做对了:

我们有一个实现IDisposal模式的大型资源类。它应该(通过设计)以某种方式实现,这使得它可以被多次调用(即使我们尝试将其称为恰好一次)。我们还实现了一个终结器,它也调用Dispose()方法 - 就像备份一样。如果手动调用,Dispose()也会调用GC.SuppressFinalize(this)。

有几个处理模式的例子。他们中的大多数在处置代码的 end 处调用GC.SuppressFinalize(this)。有人声称,最好在Dispose()方法的开头调用它,任何清理之前调用它。后者争辩说,这将确保GC不会同时调用终结器,而我们仍在清理。

问题:
看来,将GC.SuppressFinalize放在一开始并没有做得更好?我们还有竞争条件,对吗?那么,我们宁愿以线程安全的方式实现Dispose()吗?

3 个答案:

答案 0 :(得分:4)

GC仅清除不可“访问”的对象。

执行代码的类仍然是“可达”的,因为它的this指针位于堆栈上。因此,当dispose正在执行时,将不会调用终结器。

<击>

因此,如果您在开始或结束时致电SuppressFinalize无关紧要。

正如下面的评论者所指出的那样,CLR实现似乎并没有保证您的对象在执行实例方法时不会被垃圾收集/完成。保持对象存活的唯一可能的“可靠”引用是用于在对象上调用方法的引用,但是我对JIT内部的知识不够了解,并且它的行为可能会改变。

我将在此处留下答案,以便访问以下讨论。

答案 1 :(得分:2)

虽然有时可能在一个看似实时的引用存在的情况下最终确定一个对象,但只有当其他任何内容都不会引用该对象时才会发生这种情况。 GC.SuppressFinalize(this)主动引用当前对象'this',从而保证在GC.SuppressFinalize执行之前不会完成它。此外,对象引用存在以处置对象并且可用于Dispose方法的事实保证了在Dispose开始运行之前终结器不能排队,除非对象已经死了并且某个终结器(或者它自己的终结器) ,或其他一些对象的那个)复活了它。

因为在某些情况下可以安排一个对象进行最终确定并在没有意识到的情况下复活,所以保护一个部署并最终完成冗余操作可能不是一个坏主意。但是,微软的模式不是很好。可终结对象不应包含对最终化不需要的任何对象的引用。如果一个对象拥有托管和非托管资源的混合,那么非托管资源应该被移动到它们自己的类中(有效地将它们转换为托管资源),因此主要对象除了托管资源之外什么都不会。

答案 2 :(得分:1)

Dispose不应该抛出任何异常。

我会确保Dispose中的所有代码都是线程安全的,这样如果它被调用它就不会做任何奇怪的事情。通常添加检查变量是否为null应该可以解决问题。

在微软的例子中,我只在Dispose函数结束时看到了GC.SuppressFinalize。