谁能解释一下这种终结行为

发布于 2024-12-13 14:12:34 字数 1302 浏览 2 评论 0原文

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

我本以为 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 上运行的。

非常感谢

Whilst 'investigating' finalisation (read: trying stupid things) I stumbled across some unexpected behaviour (to me at least).

I would have expected the Finalise method to not be called, whereas it gets called twice

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");
    }
}

Could anyone explain whether this behaviour is intentional and if so why?

This above code was compiled in x86 debug mode and running on the CLR v4.

Many thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

汹涌人海 2024-12-20 14:12:34

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

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

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

不幸的是,同一文档页面随后显示了一个示例,其中对尚未调用 SuppressFinalize 的对象调用了 ReRegisterForFinalize。

这有点乱。我会和文档经理一起解决这个问题。

当然,这个故事的寓意是,如果当您违反文档中描述的规则时会受到伤害,那么停止违反它们

I do not know what is causing the bizarre behaviour. However, since you are in violation of the documented usage of the method, anything can happen. The documentation for ReRegisterForFinalize says:

Requests that the system call the finalizer for the specified object for which SuppressFinalize has previously been called.

You did not previously call SuppressFinalize before you called ReRegisterForFinalize. The documentation does not say what happens in that situation, and in fact, apparently something really weird happens.

Unfortunately, that same documentation page then goes on to show an example in which ReRegisterForFinalize is called on an object for which SuppressFinalize has not been called.

This is a bit of a mess. I'll take it up with the documentation manager.

The moral of the story is, of course, if it hurts when you violate the rules described in the documentation then stop violating them.

记忆之渊 2024-12-20 14:12:34

我可以猜测...而这确实只是一个猜测。正如Eric所说,不要违反这样的规则:)这种猜测只是为了闲聊和兴趣。

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

  • 终结队列
  • 对象的标头

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

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

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

I can guess... and this really is only a guess. As Eric says, don't break the rules like this :) This guess is only for the sake of idle speculation and interest.

I suspect that there are two data structures involved:

  • A finalization queue
  • The object's header

When the GC notices that an object is eligible for garbage collection, I suspect it checks the object's header and adds the reference to the finalization queue. Your calls to SuppressFinalization are preventing that behaviour.

Separately, the finalizer thread runs through the finalization queue and calls the finalizer for everything it finds. Your calls to ReRegisterForFinalize are bypassing the normal way that the reference ends up on the queue, and adding it directly. SuppressFinalization isn't removing the reference from the queue - it's only stopping the reference from being added to the queue in the normal way.

All of this would explain the behaviour you're seeing (and which I've reproduced). It also explains why when I remove the SuppressFinalization calls I end up seeing the finalizer called three times - because in this case the "normal" path adds the reference to the finalization queue as well.

千年*琉璃梦 2024-12-20 14:12:34

有趣的数据点:

  • 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 上的终结器

  • 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");
    }
}

Interesting datapoints:

  • mono 2.10.8.1 on linux doesn't call finalizer
  • mono 2.8 on linux doesn't call finalizer: http://ideone.com/J6pl4
  • mono 2.8.1 on Win32 doesn't call finalizer
  • mono 2.6.7 on Win32 doesn't call finalizer

  • .NET 3.5 on Win32 calls finalizer twice

Test code for reference:

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");
    }
}
眼前雾蒙蒙 2024-12-20 14:12:34

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

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

您的代码并非如此。

I suspect this comes under the realm of 'undefined behaviour'. If you have a look at the documentation for ReRegisterForFinalize and SuppressFinalize, they say:

The obj parameter must be the caller of this method.

And that's not the case for your code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文