请考虑以下代码:
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);
查看更新的输出。
答案 0 :(得分:3)
更新了更新问题的答案。
通过新问题,我们将完成以下步骤。
(如果B在第4点最终确定,则有可能在第5点之前收集它,因为等待结束的B同时保留B和Ba的收集,等待完成的Ba不会影响B的收集)。
发生的事情是4到5之间的顺序是B.a最终确定,然后B完成。由于WeakReference对象的引用不是普通引用,因此它需要自己的清理代码来释放它的GCHandle。显然它不能依赖于正常的GC集合行为,因为它的引用的全部意义在于它们不遵循正常的GC集合行为。
现在B的终结器已经运行,但由于Ba的终结器的行为是释放它的引用,它为IsAlive返回false(或者在1.1之前的.NET中,如果我记得版本正确,则抛出错误)。
答案 1 :(得分:2)
这方面的关键问题是您在终结器中访问引用字段。 底层问题是WeakReference
本身已经(或者可以,不可预测)已经被收集(因为收集顺序是非-deterministic)。简单地说:WeakReference
不再存在,您在幽灵对象上查询IsValid
/ Target
等。
因此,访问此对象完全是不可靠和脆弱的。终结者应该只与直接值类型状态 - 句柄等交谈。任何引用(除非你知道它将永远超出被销毁的对象)应该被处理不信任和避免。
如果相反,我们传入WeakReference
并确保未收集WeakReference
,那么一切正常;以下应该显示一个成功(我们已经在WeakReference
中传递的那个),一个失败(我们为此对象创建了WeakReference
,因此它有资格收集同时 as 对象):
using System;
class Program
{
static void Main(string[] args)
{
A a = new A();
CreateB(a);
WeakReference weakRef = new WeakReference(a);
CreateB(weakRef);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.KeepAlive(a);
GC.KeepAlive(weakRef);
Console.ReadKey();
}
private static void CreateB(A a)
{
B b = new B(a);
}
private static void CreateB(WeakReference a)
{
B b = new B(a);
}
}
class A
{ }
class B
{
private WeakReference a;
public B(WeakReference a)
{
this.a = a;
}
public B(A a)
{
this.a = new WeakReference(a);
}
~B()
{
Console.WriteLine("a.IsAlive: " + a.IsAlive);
Console.WriteLine("a.Target: " + a.Target);
}
}
<击> 是什么让你说它没有被收集?它看起来符合条件....活动对象上没有字段保存它,并且该变量永远不会被读取超过该点(事实上该变量可能已被编译器优化掉了,所以没有“当地“在IL”。
您可能需要 GC.KeepAlive(a)
底部的Main
来阻止它。
答案 2 :(得分:2)
这确实有点奇怪,我不能说,我有答案,但这是我到目前为止所发现的。举个例子,我在调用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
的调用反过来也会导致修改弱引用。这可以解释观察到的行为,但我不能说这是否是它在所有情况下的表现。
答案 3 :(得分:1)
为什么它是假的而且是空的? A尚未收集。
你肯定不知道。 GC可以在不再需要时立即收集它 - 在这种情况下,它在填充到WeakReference之后就会立即收集。
顺便说一下,Raymond Chen最近有blog post这个话题。
答案 4 :(得分:1)
垃圾收集器已确定a
已死,因为GC.collect()之后不再引用它。如果您将代码更改为:
GC.Collect();
GC.WaitForPendingFinalizers();
System.Console.WriteLine("And here's:"+a);
在B的最终确定期间,您会发现a
存活。
答案 5 :(得分:0)
即使WeakReference
未实现IDisposable
,它也会使用非托管资源(GCHandle
)。当WeakReference
被放弃时,它必须确保在WeakReference
本身被垃圾收集之前释放资源;如果没有,系统将无法知道不再需要GCHandle
。要处理此问题,WeakReference
会在其GCHandle
方法中释放Finalize
(从而使自身无效)。如果在执行试图使用Finalize
的{{1}}方法之前发生这种情况,则后一种方法将无法获得WeakReference
以前的目标。
WeakReference
的构造函数接受一个参数,该参数指示一旦其目标有资格立即完成(参数值WeakReference
),或者仅当其目标符合条件时,其目标是否应该失效湮灭(参数值false
)。我不确定该参数是否会导致true
本身在一个GC循环中复活,但这可能是可能的。
否则,如果您使用的是.net 4.0,则有一个名为WeakReference
的类可能会有所帮助;它允许链接各种对象的生命周期。你可以有一个finalizable对象,它持有你想要弱引用的对象的强引用,并且对可终结对象的唯一引用存储在ConditionalWeakTable
中,由弱引用对象键入是理想的。后一个对象(ConditionalWeakTable
Key`的Value
做了;然后它可以用它所拥有的强引用做一些合适的事情。