C# 为什么WeakReference在析构函数中是无用的?
考虑以下代码:C# 为什么WeakReference在析构函数中是无用的?,c#,weak-references,C#,Weak References,考虑以下代码: class Program { static void Main(string[] args) { A a = new A(); CreateB(a); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("And here's:" + a); GC.KeepAlive(a); }
class Program
{
static void Main(string[] args)
{
A a = new A();
CreateB(a);
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("And here's:" + a);
GC.KeepAlive(a);
}
private static void CreateB(A a)
{
B b = new B(a);
}
}
class A
{ }
class B
{
private WeakReference a;
public B(A a)
{
this.a = new WeakReference(a);
}
~B()
{
Console.WriteLine("a.IsAlive: " + a.IsAlive);
Console.WriteLine("a.Target: " + a.Target);
}
}
具有以下输出:
a.IsAlive: False
a.Target:
And here's:ConsoleApp.A
为什么它是假的和空的?A还没有收到
编辑:哦,你这个小信仰的人
我添加了以下行:
Console.WriteLine("And here's:" + a);
GC.KeepAlive(a);
请参阅更新的输出。其中的关键问题是,在终结器期间,您正在访问一个参考字段。潜在的问题是,
WeakReference
本身已经(或者可以不可预测地)被收集(因为收集顺序是不确定的)。简单地说:WeakReference
不再存在,您可以在一个重影对象上查询IsValid
/Target
等
因此,访问这个对象是不可靠和脆弱的。终结器应仅与直接值类型状态-句柄等对话。任何引用(除非您知道它将始终在被销毁的对象之外)都应受到不信任的对待并避免使用
相反,如果我们传入WeakReference
,并确保未收集WeakReference
,则一切正常;下面应该显示一个成功(我们在WeakReference
中通过的一个),和一个失败(我们仅为此对象创建了WeakReference
,因此它可以与对象同时收集):
你为什么说它没有被收集?它看起来合格。。。。活动对象上没有字段保存它,并且变量的读取永远不会超过该点(事实上,该变量可能已经被编译器优化掉,因此IL中没有“局部”变量)
您可能需要在Main
的底部添加一个GC.KeepAlive(a)
来停止它
为什么它是假的和空的?A还没有收到
你不一定知道。GC可以在不再需要它时立即收集它——在本例中,这是在它被塞进WeakReference之后
顺便说一句,Raymond Chen最近对这个话题有了一个新的看法。垃圾收集器已确定
a
已死亡,因为它在GC.collect()之后不再被引用。如果将代码更改为:
GC.Collect();
GC.WaitForPendingFinalizers();
System.Console.WriteLine("And here's:"+a);
在B的定稿过程中,您会发现
a
处于活动状态。更新问题的更新答案
对于新问题,我们将执行以下步骤
现在B的Finalizer正在运行,但由于B.a的Finalizer的行为是释放其引用,它为IsAlive返回false(或者在1.1之前的.NET中,如果我记得版本正确,则抛出一个错误)。这确实有点奇怪,我不能说,我已经找到了答案,但这是我迄今为止的发现。根据您的示例,我在调用
GC.Collect
之前立即连接了WinDbg。此时,弱引用会像预期的那样保留实例
接下来,我挖掘了WeakReference
的实际实例,并在引用本身上设置了一个数据断点。从这一点开始,调试器在mscorwks处中断!WKS::FreeWeakHandle+0x12
(将句柄设置为null),托管调用堆栈如下所示:
OS Thread Id: 0xf54 (0)
ESP EIP
0045ed28 6eb182d3 [HelperMethodFrame: 0045ed28] System.GC.nativeCollectGeneration(Int32, Int32)
0045ed80 00af0c62 System.GC.Collect()
0045ed84 005e819d app.Program.Main(System.String[])
0045efac 6eab1b5c [GCFrame: 0045efac]
这似乎表明,对
GC.Collect
的调用反过来也会修改弱引用。这可以解释观察到的行为,但我不能说它在所有情况下是否都会这样 尽管WeakReference
没有实现IDisposable
,但它确实使用了非托管资源(aGCHandle
)。当一个WeakReference
被放弃时,它必须确保在WeakReference
本身被垃圾回收之前释放资源;如果没有,系统将无法知道不再需要GCHandle
。为了解决这个问题,一个WeakReference
在其Finalize
方法中释放其GCHandle
(从而使其自身无效)。如果这发生在尝试使用WeakReference
的Finalize
方法执行之前,则后一种方法将无法获取WeakReference
的前一个目标
WeakReference
的构造函数接受一个参数,该参数指示在其目标符合立即终止条件时(参数值false
),还是仅当其目标符合消除条件时(参数值true
),其目标是否应立即失效。我不确定该参数是否会导致WeakReference
本身在一个GC周期内重新启动,但这可能是一种可能性
否则,如果你是
OS Thread Id: 0xf54 (0)
ESP EIP
0045ed28 6eb182d3 [HelperMethodFrame: 0045ed28] System.GC.nativeCollectGeneration(Int32, Int32)
0045ed80 00af0c62 System.GC.Collect()
0045ed84 005e819d app.Program.Main(System.String[])
0045efac 6eab1b5c [GCFrame: 0045efac]