GC.WaitForPendingFinalizers 之后未调用终结器

Him*_*ere 3 c# events garbage-collection

我读到了Eric Lippert 的精彩解释,内容是关于当一个对象通过事件引用另一个对象时,何时会被垃圾收集。

为了证明埃里克所说的,我尝试了这段代码:

using System;
                
public class Program
{
    public static void Main()
    {
        {
            var myClass = new GCClass();
            LongLivingClass.MyEvent += myClass.HandleEvent;
        } // LongLivingClass holds a reference to myClass
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        // expect: finalizer of GCLass not run

        {
            var myClass = new GCClass();
            myClass.MyEvent += LongLivingClass.HandleEvent;
        } // myClass can easily be GCed here
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        // expect: finalizer should run

        Console.WriteLine("Finish");
    }
}


class GCClass
{
    public event EventHandler MyEvent;
    public void HandleEvent(object o, EventArgs e) { }
    ~GCClass() { Console.WriteLine("Finalizer hit"); }
}    
public class LongLivingClass
{
    public static event EventHandler<EventArgs> MyEvent;
    public static void HandleEvent(object o, EventArgs e) { }
}
Run Code Online (Sandbox Code Playgroud)

正如我所期望的,第一个GC.Collect块不会完成任何事情,因为该对象只是被集合引用LongLvongClass并因此在集合中幸存下来。

然而,第二个块也不会调用终结器,尽管myClass有资格进行收集,并且我们甚至正在等待终结器发生。然而,我的终结器没有被击中。与GC.Collect() 和 Finalize一样,我希望这里会命中终结器。我在其中放置了一个断点,以表明这一点。

我这里哪里出错了?我想myClass第二个代码块中没有收集,但我不知道为什么。

Eri*_*ert 6

我这里哪里出错了?

简洁版本:

这里要认识到的重要一点是,C# 与C++ 不同,C++ 的意思}是“我们现在必须运行局部变量的析构函数”。

C# 可以自行决定增加任何局部变量的生命周期,而且它一直这样做。因此,您永远不应该仅仅因为控制进入变量超出范围的区域而期望收集局部变量的内容。

长版:

{ }定义嵌套局部变量声明空间的规则是C#语言的规则;它绝对不是 C# 编译成的 IL 的功能!

这些局部变量声明空间的存在是为了让您可以更好地组织代码,并且 C# 编译器可以发现您在局部变量不在作用域内时使用局部变量的错误。但是 C# 不会在块的底部发出 IL,指出“以下局部变量现在超出范围” { }

允许抖动注意到您的第二个myClass从未被读取,因此可以在最终写入后从根集中删除。但事实上,允许这样做并不要求它这样做,而且通常也不会这样做。

“通常”在那里做一些繁重的工作,因为当然,允许抖动会缩短本地的寿命。考虑一下这种糟糕的情况:

{
    ManagedHandle handle = NativeObject.Open();
    SomeNativeCode.CacheTheUnderlyingHandle(handle);
    // handle is still in scope but the jitter is allowed to
    // realize it is never read again at this point and remove the
    // local from the root set. If you have the misfortune that you
    // get a GC and finalize right now then the resource is deallocated
    // by the time we get to:
    SomeNativeCode.UseTheCachedHandle();
}        
Run Code Online (Sandbox Code Playgroud)

如果你遇到这种不幸的情况,可以KeepAlive强迫当地人活下去。

  • “活跃”的局部变量是根,但抖动在决定局部变量是否活跃(根)或死亡(不是根)方面具有很大的自由裁量权。如果本地可以被再次读取,则它必须保持活着,但反之则不然;如果不再读取,则本地允许死亡,但如果不再读取,则不*要求*死亡。 (2认同)