简体   繁体   English

为什么在对象上调用终结器

[英]Why is finalizer called on object

Here is example program that exhibits surprising finalization behavior: 这是一个展示令人惊讶的终结行为的示例程序:

class Something
{
    public void DoSomething()
    {
        Console.WriteLine("Doing something");
    }
    ~Something()
    {
        Console.WriteLine("Called finalizer");
    }
}

namespace TestGC
{
    class Program
    {
        static void Main(string[] args)
        {
           var s = new Something();
           s.DoSomething();
           GC.Collect();
           //GC.WaitForPendingFinalizers();
           s.DoSomething();
           Console.ReadKey();
        }
    }
}

If I run the program, what gets printed is: 如果我运行该程序,打印的是:

Doing something
Doing something
Called finalizer

This appears as expected. 这似乎符合预期。 Because there is a reference to s after the call to GC.Collect() , s is not a garbage. 因为在调用GC.Collect()之后有一个对s的引用,所以s不是垃圾。

Now remove comments from the line //GC.WaitForPendingFinalizers(); 现在删除//GC.WaitForPendingFinalizers();行中的//GC.WaitForPendingFinalizers(); build and run the program again. 再次构建并运行程序。

I would expect nothing to change in the output. 我希望输出中没有任何改变。 This because I read that if object is found to be a garbage and it has a finalizer, it will be put on finalizer queue . 这是因为我读到如果发现对象是垃圾并且它有终结器,它将被放在终结器队列中 Since object is not a garbage, then is seems logical that it should not be put on finalizer queue. 由于对象不是垃圾,因此它不应该放在终结器队列上似乎是合乎逻辑的。 Thus the line commented out should do nothing. 因此,注释掉的那条线应该什么都不做。

However, the output of the program is: 但是,该计划的输出是:

Doing something
Called finalizer
Doing something

Can somebody help my understanding of why finalizer gets called? 有人可以帮助我理解为什么终结器会被调用吗?

I can't reproduce the problem on my laptop, but your DoSomething method doesn't use any fields within the object. 我无法在笔记本电脑上重现问题,但您的DoSomething方法不使用对象中的任何字段。 That means that the object can be finalized even while DoSomething is running . 这意味着即使DoSomething正在运行,也可以最终确定对象。

If you change your code to: 如果您将代码更改为:

class Something
{
    int x = 10;

    public void DoSomething()
    {
        x++;
        Console.WriteLine("Doing something");
        Console.WriteLine("x = {0}", x);
    }
    ~Something()
    {
        Console.WriteLine("Called finalizer");
    }
}

... then I suspect you will always see DoingSomething printed twice before "Called finalizer" - although the final "x = 12" may be printed after "Called finalizer". ...然后我怀疑你总会看到DoingSomething “叫终结”前两次印刷-虽然最后的“X = 12”可以“叫终结”后打印。

Basically, finalization can be somewhat surprising - I very rarely find myself using it, and would encourage you to avoid finalizers wherever possible. 基本上,最终确定可有点让人吃惊-我很少使用它发现自己,并会鼓励你,以避免终结尽可能。

Jon's answer is of course correct. 乔恩的回答当然是正确的。 I would add to it that the C# specification calls out that the compiler and runtime are allowed to (but not required to) notice that the reference contained in a local variable is never again dereferenced, and at that point the garbage collector is allowed to treat the object as dead if the local is the last living reference. 我想补充一点,C#规范调用允许编译器和运行时(但不是必须 )注意到局部变量中包含的引用永远不再被取消引用,并且此时允许垃圾收集器处理如果本地是最后的生活参考,则该对象为死亡。 Therefore the object can be collected and the finalizer run even though there appears to be a reference in a living local variable. 因此,即使在生活局部变量中似乎存在引用,也可以收集对象并且终结器运行。 (And similarly the compiler and runtime are allowed to make locals live longer if they so choose.) (同样地,如果他们愿意,允许编译器和运行时使本地人的寿命更长 。)

Given this fact you can end up in bizarre situations. 鉴于这一事实,你最终可能会陷入奇怪的境地。 For example, it is possible for a finalizer to be executing on the finalizer thread while the object's constructor is running on a user thread . 例如, 当对象的构造函数在用户线程上运行时,终结器可以在终结器线程上执行。 If the runtime can determine that "this" is never dereferenced again then the object can be treated as dead the moment the constructor is done mutating fields of "this". 如果运行时可以确定“this”永远不再被解除引用,那么在构造函数完成“this”字段变异的那一刻,该对象可以被视为死。 If the constructor then does additional work -- say, mutating global state -- then that work can be done as the finalizer is running. 如果构造函数然后执行其他工作 - 比如改变全局状态 - 那么可以在终结器运行时完成该工作。

This is just yet another reason why writing a correct finalizer is so difficult that you probably should not do so. 这也是为什么编写正确的终结器非常困难以至于你可能不应该这样做的另一个原因。 In a finalizer everything you know is wrong . 在终结器中你知道的一切都是错的 Everything you're referring to could be dead, you're on a different thread, the object might not be fully constructed, possibly none of your program invariants are actually maintained. 你所指的一切都可能已经死了,你在一个不同的线程,对象可能没有完全构造,可能没有你的程序不变量实际维护。

Your DoSomething() is so trivial that it's likely to get inlined. 你的DoSomething()是如此微不足道,它可能会被内联。 After it's been inlined, there's nothing that still has a reference to the object, so there's nothing preventing it from being garbage collected. 在内联之后,没有任何东西仍然可以引用该对象,因此没有什么能阻止它被垃圾收集。

GC.KeepAlive() is designed specifically for this scenario. GC.KeepAlive()专为此方案而设计。 It can be used if you want to prevent an object from being garbage collected. 如果要防止对象被垃圾回收,可以使用它。 It does nothing, but the garbage collector doesn't know that. 它什么都不做,但垃圾收集器不知道。 Call GC.KeepAlive(s); 调用GC.KeepAlive(s); at the end of Main to prevent it from being finalised earlier. Main的末尾,以防止它提前完成。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM