在Dispose()方法中,GC.SuppressFinalize(this)的目的是什么?

34

我有以下代码:

public void Dispose()
{
    if (_instance != null)
    {
        _instance = null;
        // Call GC.SupressFinalize to take this object off the finalization
        // queue and prevent finalization code for this object from
        // executing a second time.
        GC.SuppressFinalize(this);
    }
}

虽然有一条注释说明了那个与GC相关的调用的目的,但我仍然不明白为什么需要它。

对象在所有实例停止存在(例如,在using块中使用)时难道不应该被清除吗?

在哪些使用场景中会发挥重要作用?

5个回答

40

实现 Dispose() 模式时,您可能还需要在类中添加一个终结器(finalizer),该终结器调用 Dispose()。这是为了确保 Dispose() 总是被调用,即使客户端忘记调用它。

为防止释放方法运行两次(如果对象已经被处理),可以添加 GC.SuppressFinalize(this);。文档提供了一个 示例

class MyResource : IDisposable
{
    [...]

    // This destructor will run only if the Dispose method 
    // does not get called.
    ~MyResource()      
    {
        // Do not re-create Dispose clean-up code here.
        // Calling Dispose(false) is optimal in terms of
        // readability and maintainability.
        Dispose(false);
    }

    // Implement IDisposable.
    // Do not make this method virtual.
    // A derived class should not be able to override this method.
    public void Dispose()
    {
        Dispose(true);
        // This object will be cleaned up by the Dispose method.
        // Therefore, you should call GC.SupressFinalize to
        // take this object off the finalization queue 
        // and prevent finalization code for this object
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        // Check to see if Dispose has already been called.
        if(!this.disposed)
        {
            // If disposing equals true, dispose all managed 
            // and unmanaged resources.
            if(disposing)
            {
                // Dispose managed resources.
                component.Dispose();
            }

            // Call the appropriate methods to clean up 
            // unmanaged resources here.
            resource.Cleanup()          
        }
        disposed = true;         
    }
}

17
根据我的经验,你可能会添加一个终结器。但很少这样做。有很多情况下,实现IDisposable而不需要终结器是很有道理的。 - Jon Skeet
@Jon Skeet:难道添加终结器不是确保Dispose()总是被调用的方法吗?如果不能确定Dispose总是被调用,那么未托管的内存或其他本机资源将如何清理? - Dirk Vollmar
好的,但是根据示例来看,如果用户代码(或使用块)没有调用Dispose(),那么如何释放管理资源?当终结器运行时,GC会自动释放它们吗? - mr.b
1
@mr. b:在我的示例中,终结器只会处理未托管的资源。所有托管的资源都将在某个时刻被垃圾回收器释放。 - Dirk Vollmar
14
如果您直接拥有非托管资源,则只需要使用终结器。如果您只是拥有对其他IDisposable实现的引用,则不需要使用终结器。SafeHandle几乎总是不需要使用终结器,因为它可以自动进行处理(以各种方式)来完成句柄的终止。 - Jon Skeet
1
比确保终结器不会被调用两次更重要的是确保在外部对象处理其“Dispose”时不会调用任何内部对象的终结器。调用GC.SuppressFinalize的成本与调用GC.KeepAlive的成本相差无几,如果将其中任何一个放在最后,都将确保在“Dispose”完成之后内部对象不再具有终结资格。 - supercat

36

垃圾回收: GC会在对象不再被引用时回收所使用的内存。

Dispose: IDisposable接口中的一个方法,程序员可以通过直接或间接地使用using块调用该方法来释放所有托管和非托管资源。

Finalizer: 一个方法用于释放所有非托管资源。在回收内存之前,GC会调用这个方法。

托管资源: 实现IDisposable接口的任何.NET类,如Streams和DbConnections。

非托管资源: 包装在托管资源类中的东西。Windows句柄是最简单的例子。


现在来回答您的问题:

GC维护了一个列表(Finalization Queue),其中包含了所有类声明了Finalizer(~ClassName在C#中)的对象。创建对象时,将其放入此队列中。GC定期运行以检查程序中是否有任何无法访问的对象。然后它会检查是否有不可访问对象从Finalization Queue中引用,将这些对象放入另一个称为“可达队列”的队列中,而其余对象则被丢弃。使用单独的线程运行Freacheable队列中对象的Finalize方法。

下次GC运行时,它会发现一些先前在可达队列中的对象已经被Finalized,因此可以准备回收。请注意,GC需要至少两个周期(或更多,如果有很多Finalization需要执行)来消除具有Finalizer的对象,这会产生一些性能损失。

SuppressFinalize方法仅在对象头中设置一个标志,表示不必运行终结器。这样,GC就可以立即回收对象的内存。根据上述定义,Dispose方法与终结器执行相同的操作(以及更多),因此如果执行了该方法,则不再需要终结器。使用SuppressFinalize方法,您可以通过通知GC来保存一些工作。此外,现在您不必在终结器中实现检查以避免重复释放。唯一的问题是Dispose不保证运行,因为调用它是程序员的责任,这就是为什么有时我们需要使用终结器的原因。
话虽如此,你很少需要编写终结器,因为对于绝大多数常规的非托管资源,都已经存在托管包装器,并且托管资源必须通过从自己的Dispose方法和仅从那里调用它们的Dispose方法来释放!在终结器中,您决不能调用Dispose方法。
进一步阅读:

GC.SuppressFinalize 的另一个目的是充当 GC.KeepAlive()。即使一个类没有 finalizer,如果该类持有对其他具有 finalizer 的对象的引用,则 GC.KeepAlive() 也可能很重要,并且还将确保任何可能标识对象的 WeakReference 在执行 GC.SuppressFinalize()GC.KeepAlive() 后仍然保持活动状态。 - supercat

5

可被终结的对象会在第一次GC运行后幸存。

通常,当垃圾回收器检测到一个对象不可达时,就会回收它。如果该对象可以进行终结,则垃圾回收器不会回收它;相反,它仍然被认为是可达的(以及所有该对象引用的对象等),并将其安排进行终结操作。只有在终结后再次被发现不可达时,该对象才会被回收。

这意味着可终结的对象会产生额外的代价:需要将对象保留在内存中更长时间。因此,你看到的调用语句:当不需要时抑制终结是值得的。在这里,该对象使用终结来确保它总是在某个时刻“被处理”。当它被显式地处理时,就不需要再进行终结了。


1
我从未听说过这种特定的行为 - 有参考资料吗,例如MSDN文档之类的东西? - Aaronaught
2
@Aaronaught:这篇文章的“Finalization Internals”部分是一个很好的概述:http://msdn.microsoft.com/en-us/magazine/bb985010.aspx - LukeH

2
如果您的类型实现了终结器(~MyType() { }),它会阻止垃圾收集器运行它。这在您的终结器负责管理非托管类型,但用户已经调用Dispose()(明确地或通过using() { }块),释放这些非托管类型时使用。

0xA3的回答更好一些,因为在这种情况下,您也可以处理托管类型... - Jesse C. Slicer
没有什么阻止你在终结器中尝试处理托管类型,但这并不是一个好主意。终结顺序是不确定的,因此可能会出现子对象在其父对象之前被清理的情况等。 - LukeH

0

来自 MSDN: GC.SuppressFinalize:

该方法在对象头中设置一个位,系统在调用终结器时会检查该位。 obj参数必须是此方法的调用者。

实现IDisposable接口的对象可以从IDisposable.Dispose方法调用此方法,以防止垃圾收集器在不需要的对象上调用Object.Finalize。

通常情况下,如果您的对象不引用其他对象,只是离散类型,或者已将任何对象引用重置为NULL,则应使用此选项。


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