在 GC 之后未调用终结器.等待挂起终结器



我读到了埃里克·利珀特(Eric Lippert)的这个精彩解释,关于当一个物体通过事件引用另一个物体时,被垃圾收集。

为了证明 Eric 所说的话,我尝试了以下代码:

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) { }
}

正如我所料,第一个GC.Collect块并没有完成任何内容,因为该对象只是被LongLvongClass引用,因此在集合中幸存下来。

然而,第二个区块也不会调用终结器,尽管myClass有资格收集,我们甚至在等待终结者发生。但是,我的终结器没有被击中。如来自GC。Collect() 和 Finalize 我希望终结器会在这里被击中。我在里面放了一个断点,以表明这一点。

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

我哪里出错了?

简短版本:

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

允许 C# 自行决定增加任何局部变量的生存期,并且它始终这样做。因此,您永远不应该期望仅仅因为控件进入变量超出范围的区域而收集局部变量的内容。

长版本:

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

这些局部变量声明空间的存在是为了更好地组织代码,以便 C# 编译器可以找到在局部变量不在范围内时使用局部变量的 bug。但是 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();
}        

如果您处于这种不幸的情况,请使用KeepAlive来强迫当地人活下去。

最新更新