任何人都可以解释这种最终化行为

时间:2011-11-04 14:44:43

标签: c# .net-4.0 clr destructor finalizer

虽然'调查'最终确定(阅读:尝试愚蠢的事情)我偶然发现了一些意想不到的行为(至少对我来说)。

我原本期望不会调用Finalize方法,而是调用它两次

class Program
{
    static void Main(string[] args)
    {
        // The MyClass type has a Finalize method defined for it
        // Creating a MyClass places a reference to obj on the finalization table.
        var myClass = new MyClass();

        // Append another 2 references for myClass onto the finalization table.
        System.GC.ReRegisterForFinalize(myClass);
        System.GC.ReRegisterForFinalize(myClass);
        // There are now 3 references to myClass on the finalization table.

        System.GC.SuppressFinalize(myClass);
        System.GC.SuppressFinalize(myClass);
        System.GC.SuppressFinalize(myClass);

        // Remove the reference to the object.
        myClass = null;

        // Force the GC to collect the object.
        System.GC.Collect(2, System.GCCollectionMode.Forced);

        // The first call to obj's Finalize method will be discarded but
        // two calls to Finalize are still performed.
        System.Console.ReadLine();
    }
}

class MyClass
{
    ~MyClass()
    {
        System.Console.WriteLine("Finalise() called");
    }
}

有人可以解释这种行为是否是故意的,为什么会这样?

以上代码是在x86调试模式下编译并在CLR v4上运行。

非常感谢

4 个答案:

答案 0 :(得分:16)

我不知道造成这种怪异行为的原因。但是,由于您违反了该方法的记录用法,因此可能发生任何事情。 ReRegisterForFinalize的文档说:

  

请求系统调用先前已调用SuppressFinalize的指定对象的终结器。

在调用ReRegisterForFinalize之前,您之前没有调用SuppressFinalize。文档没有说明在那种情况下会发生什么,事实上,显然发生了一些非常奇怪的事情。

不幸的是,相同的文档页面继续显示一个示例,其中在尚未调用SuppressFinalize的对象上调用ReRegisterForFinalize。

这有点乱。我将与文档管理员联系。

当然,如果你违反文件中描述的规则,那么这个故事的道德就是伤害,然后停止违反它们

答案 1 :(得分:10)

我可以猜测 ......这真的只是猜测。正如埃里克所说,不要违反这样的规则:)这个猜测只是为了闲置的猜测和兴趣。

我怀疑涉及两种数据结构:

  • 结束队列
  • 对象的标题

当GC注意到一个对象符合垃圾收集条件时,我怀疑它会检查对象的头并将引用添加到终结队列。您对SuppressFinalization的来电阻止了这种行为。

另外,终结器线程遍历终结队列并为找到的所有内容调用终结器。您对ReRegisterForFinalize的调用绕过了引用最终在队列中的正常方式,并直接添加它。 SuppressFinalization不会从队列中删除引用 - 它只是阻止引用以正常方式添加到队列中。

所有这些都可以解释你所看到的行为(以及我所复制的行为)。它还解释了为什么当我删除SuppressFinalization调用时,我最终看到终结器名为次 - 因为在这种情况下,“正常”路径也会将引用添加到终结队列中。

答案 2 :(得分:5)

有趣的数据点:

  • mono 2.10.8.1在linux上没有调用终结器
  • Linux上的
  • mono 2.8不会调用终结器:http://ideone.com/J6pl4
  • Win32上的
  • mono 2.8.1不会调用终结器
  • Win32上的mono 2.6.7不会调用终结器

  • Win32上的.NET 3.5调用终结器两次

测试代码供参考:

class Program
{
    static void Main(string[] args)
    {
        // The MyClass type has a Finalize method defined for it
        // Creating a MyClass places a reference to obj on the finalization table.
        var myClass = new MyClass();

        // Append another 2 references for myClass onto the finalization table.
        System.GC.ReRegisterForFinalize(myClass);
        System.GC.ReRegisterForFinalize(myClass);
        // There are now 3 references to myClass on the finalization table.

        System.GC.SuppressFinalize(myClass);
        System.GC.SuppressFinalize(myClass);
        System.GC.SuppressFinalize(myClass);

        // Remove the reference to the object.
        myClass = null;

        // Force the GC to collect the object.
        System.GC.Collect(2, System.GCCollectionMode.Forced);

        // The first call to obj's Finalize method will be discarded but
        // two calls to Finalize are still performed.
    }
}

class MyClass
{
    ~MyClass()
    {
        System.Console.WriteLine("Finalise() called");
    }
}

答案 3 :(得分:2)

我怀疑这属于“未定义行为”的范畴。如果您查看ReRegisterForFinalizeSuppressFinalize的文档,他们会说:

  

obj 参数必须是此方法的调用者。

而你的代码并非如此。