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

Ric*_*lly 6 c# clr destructor .net-4.0 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

任何人都可以解释这种行为是否是故意的,如果是这样,为什么

上面的代码是在x86调试模式下编译的,并在CLR v4上运行.

非常感谢

Eri*_*ert 16

我不知道是什么导致了奇怪的行为.但是,由于您违反了该方法的记录用法,因此可能发生任何事情.ReRegisterForFinalize的文档说:

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

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

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

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

当然,这个故事的寓意是,当你违反文档中描述的规则时会受到伤害,然后停止违反它们.


Jon*_*eet 10

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

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

  • 终结队列
  • 对象的标题

当GC注意到一个对象有资格进行垃圾收集时,我怀疑它会检查对象的头并将引用添加到终结队列.你的电话SuppressFinalization会阻止这种行为.

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

所有这些都可以解释你所看到的行为(以及我所复制的行为).这也解释了为什么当我删除SuppressFinalization电话我最终看到称为终结3倍-因为在这种情况下,"正常"的路径添加参考终结队列为好.

  • 好的电话,乔恩.查看MS发布的CLI源代码,内部`ReRegisterForFinalize()`方法中有一个`if`语句,如果我已经标记为已完成,请删除该标记.如果没有,只需将我添加到队列." 因此,如果您在没有先压制的情况下调用ReReg两次,则该对象将被添加到队列中两次.然后,抑制清除该位三次(这没有什么,真的,因为该位从未设置为开始!)无论如何,一如既往的辉煌推测. (2认同)

seh*_*ehe 5

有趣的数据点:

  • Linux上的mono 2.10.8.1不会调用终结器
  • 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");
    }
}
Run Code Online (Sandbox Code Playgroud)