为什么垃圾收集器以错误的顺序获取对象?

时间:2010-11-12 10:18:17

标签: c# garbage-collection destructor

我有一个带有两个类A和B的应用程序。类A在类B的引用内部。类的析构函数执行一些资源清理但是必须按正确的顺序调用它们,首先是析构函数A,然后是B的析构函数。

发生的事情是,以某种方式首先调用B的析构函数,然后A的析构函数崩溃,因为它试图从已处置的对象执行方法。

GC的这种行为是否正确?我希望GC检测到A有对B的引用,然后先调用A析构函数。我是对的吗?

感谢队友!

PD:如果对析破器/终结器/处理器等有疑问,那就是我们所拥有的:

~A()
{
    this.Dispose();
}

~B()
{
    this.Dispose();
}    

5 个答案:

答案 0 :(得分:14)

正如其他人所说,你的终结者是错误的,错误的,错误的。你不能简单地在终结器中调用Dispose并期望好事发生。阅读实施一次性模式的正确方法。

做到这一点是你必须做的工作的开始,而不是结束。除了这里所有其他正确的答案,我注意到:

  • 终结器可以(通常会)在不同的线程上运行。完成对特定线程具有亲缘关系的资源是危险的,如果不小心,可能会遇到死锁等等。

  • 终结可以通过将对它的引用分配给活动对象中的变量来“复活”死对象。不要那样做。令人难以置信的是令人困惑。

  • 终结器可以在发生线程中止异常时部分构造的对象上运行。你不能假设任何对于完全构造的对象都是正确的不变量对于正在最终确定的对象都是正确的。

出于所有这些原因,正确编写终结器非常困难。避免,避免,避免。

答案 1 :(得分:13)

GC是不确定的:不能保证以任何特定的顺序,或在任何时间,甚至永远垃圾收集您的对象 - 它可能决定在程序完成后保留它们,如果它想要的话至。因此,终结者很少使用或使用。

如果您想要确定性最终确定,则应使用IDisposable pattern

答案 2 :(得分:9)

来自http://msdn.microsoft.com/en-us/magazine/bb985010.aspx

  

运行时不会对调用Finalize方法的顺序做出任何保证。例如,假设有一个对象包含指向内部对象的指针。垃圾收集器检测到两个对象都是垃圾。此外,假设首先调用内部对象的Finalize方法。现在,允许外部对象的Finalize方法访问内部对象并在其上调用方法,但内部对象已经完成,结果可能是不可预测的。因此,强烈建议Finalize方法不要访问任何内部成员对象。

答案 3 :(得分:2)

你不应该编写依赖于C#中的解构器的代码,它们可能永远不会运行,所以不能依赖它们。

答案 4 :(得分:1)

编写终结器的正确方法是不在相关对象上调用任何东西,你应该只通过终结器销毁非托管资源,正如你所指出的那样,事情发生在乱序中。

正如其他人所说,这是作为规范的一部分记录的,因此这不是一个错误,也不会改变为“做正确的事”。特别是,考虑两个对象都引用另一个对象,运行时无法确定这里的正确顺序。

无论如何,这是你应该如何实现终结器。请注意,如果需要销毁非托管资源,则应该只实现终结器(注意,有些边缘情况,可能想要一个非常特殊用途的类的终结器,但我不会这里提到这些不仅仅是这个):

public class Test : IDisposable
{
    ~Test()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        // TODO: Make sure calling Dispose twice is safe
        if (disposing)
        {
            // call Dispose method on other objects
            GC.SuppressFinalize(this);
        }

        // destroy unmanaged resources here
    }
}

您可以在此处找到更多信息:Implementing a Dispose Method