为什么弱引用在析构函数中没有用?
考虑以下代码:
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
为什么它是 false 和 null? A 还没收集到。
编辑:哦,你们这些小信仰。
我添加了以下行:
Console.WriteLine("And here's:" + a);
GC.KeepAlive(a);
请参阅更新的输出。
Consider the following code:
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);
}
}
With the following output:
a.IsAlive: False
a.Target:
And here's:ConsoleApp.A
Why is it false and null? A hasn't been collected yet.
EDIT: Oh ye of little faith.
I added the following lines:
Console.WriteLine("And here's:" + a);
GC.KeepAlive(a);
See the updated output.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
更新问题的更新答案。
有了新问题,我们将执行以下步骤。
(如果 B 在第 4 点完成,则有可能在第 5 点之前被收集,因为 B 等待最终确定会使 B 和 Ba 都无法被收集,而 Ba 等待最终确定不会影响 B 的收集)。
所发生的情况是,4 和 5 之间的顺序是 Ba 被最终确定,然后 B 被最终确定。由于 WeakReference 持有的对象引用不是普通引用,因此它需要自己的清理代码来释放其 GCHandle。显然它不能依赖于正常的 GC 收集行为,因为其引用的全部要点是它们不遵循正常的 GC 收集行为。
现在 B 的终结器已运行,但由于 Ba 的终结器的行为是释放其引用,因此它为 IsAlive 返回 false(或者在 1.1 之前的 .NET 中,如果我没记错版本,则会引发错误)。
Updated answer for updated question.
With the new question, we go through the following steps.
(If B was finalised at point 4 it would be possible for it to be collected before point 5, as while B awaiting finalisation keeps both B and B.a from collection, B.a awaiting finalisation does not affect B's collection).
What has happened is that the order between 4 and 5 was such that B.a was finalised and then B was finalised. Since the reference a WeakReference holds to an object is not a normal reference, it needs its own clean-up code to release its GCHandle. Obviously it can't depend on normal GC collection behaviour, since the whole point of its references is that they don't follow normal GC collection behaviour.
Now B's finaliser is run, but since the behaviour of B.a's finaliser was to release its reference it returns false for IsAlive (or in .NET prior to 1.1 if I'm remembering the versions right, throws an error).
其中的关键问题是您在终结器期间访问引用字段。 潜在问题是
WeakReference
本身已经(或者可能,不可预测)已经被收集(自收集以来)顺序是不确定的)。简单地说:WeakReference
不再存在,并且您在幽灵对象上查询IsValid
/Target
等。因此,完全访问该对象是不可靠且脆弱的。终结器应该仅与直接值类型状态 - 句柄等对话。任何引用(除非您知道它总是比被销毁的对象更长寿)应该被处理不信任并回避。
相反,如果我们传入
WeakReference
并确保WeakReference
不被收集,那么一切都会正常;下面应该显示一种成功(我们传入WeakReference
的情况),一种失败(我们仅为该对象创建WeakReference
的情况,因此它可以与对象同时被收集):你凭什么说它没有被收集?它看起来符合条件......活动对象上没有字段保存它,并且该变量永远不会在该点之后读取(实际上该变量很可能已被编译器优化掉,所以没有“ IL 中的“local”)。
您可能需要在Main
底部使用GC.KeepAlive(a)
来停止它。The key problem in this is that you are accessing a reference field during a finalizer. The underlying problem is that the
WeakReference
itself has (or can be, unpredictably) already been collected (since collection order is non-deterministic). Simply: theWeakReference
no longer exists, and you are queryIsValid
/Target
etc on a ghost object.So accessing this object at all is unreliable and brittle. Finalizers should only talk to direct value-type state - handles, etc. Any reference (unless you know it will always out-live the object being destroyed) should be treated with distrust and avoided.
If instead, we pass in the
WeakReference
and ensure that theWeakReference
is not collected, then everything works fine; the following should show one success (the one where we've passed in theWeakReference
), and one fail (where we've created theWeakReference
just for this object, thus it is eligible for collection at the same time as the object):What makes you say it isn't collected? It looks eligible.... no field on a live object holds it, and the variable is never read past that point (and indeed that variable may well have been optimised away by the compiler, so no "local" in the IL).
You might need aGC.KeepAlive(a)
at the bottom ofMain
to stop it.这确实有点奇怪,我不能说我有答案,但这是我迄今为止发现的。根据您的示例,我在调用 GC.Collect 之前立即附加了 WinDbg。此时,弱引用按预期保留实例。
接下来,我挖出了
WeakReference
的实际实例,并在引用本身上设置了数据断点。从这一点出发,调试器在mscorwks!WKS::FreeWeakHandle+0x12
处中断(将句柄设置为 null),托管调用堆栈如下所示:这似乎表明,对 < code>GC.Collect 反过来最终也会修改弱引用。这可以解释观察到的行为,但我不能说这是否是所有情况下的行为方式。
This is indeed a bit odd and I can't say, that I have the answer, but here's what I have found so far. Given your example, I attached WinDbg immediately before the call to
GC.Collect
. At this point the weak reference holds on to the instance as expected.Following that I dug out the actual instance of
WeakReference
and set a data breakpoint on the reference itself. Proceeding from this point the debugger breaks atmscorwks!WKS::FreeWeakHandle+0x12
(which sets the handle to null) and the managed call stack is as follows:This seems to indicate, that the call to
GC.Collect
in turn ends up modifying the weak reference as well. This would explain the observed behavior, but I can't say if this is how it will behave in all cases.你不确定。一旦不再需要它,GC 就会立即收集它 - 在本例中,就是在它被填充到 WeakReference 中之后。
顺便说一句,Raymond Chen 有一篇 博客文章最近关于这个话题。
You don't know that for sure. The GC can collect it as soon as it's no longer needed - which, in this case, is right after it gets stuffed into the WeakReference.
Incidentally, Raymond Chen had a blog post recently about this very topic.
垃圾收集器已确定
a
已死亡,因为在 GC.collect() 之后不再引用它。如果你把代码改成:你会发现
a
在B的终结过程中还活着。The garbage collector has determined that
a
is dead because it is not referred anymore after GC.collect(). If you change the code to:You will find
a
alive during the finalization of B.尽管
WeakReference
没有实现IDisposable
,但它确实使用了非托管资源(GCHandle
)。当WeakReference
被放弃时,它必须确保在WeakReference
本身被垃圾收集之前释放资源;如果没有,系统将无法知道不再需要GCHandle
。为了解决这个问题,WeakReference
在其Finalize
方法中释放其GCHandle
(从而使其自身无效)。如果这种情况发生在执行尝试使用WeakReference
的Finalize
方法之前,则后一个方法将无法获取WeakReference
'是以前的目标。WeakReference
的构造函数接受一个参数,该参数指示其目标是否应在其目标符合立即终止条件时立即失效(参数值false
),或者仅当其目标无效时变得有资格被消灭(参数值true
)。我不确定该参数是否会导致WeakReference
本身在一个 GC 周期内复活,但这可能是一种可能性。否则,如果您使用 .net 4.0,则有一个名为
ConditionalWeakTable
的类可能会有所帮助;它允许链接各种对象的生命周期。您可以拥有一个可终结对象,该对象持有对您想要弱引用的对象的强引用,并且对该可终结对象的唯一引用存储在ConditionalWeakTable
中,并由要弱引用的对象作为键控。需要弱引用。后一个对象(ConditionalWeakTable 条目的
Key` 完成时获得最终确定的资格;然后它可以利用它所拥有的强大参考来做一些合适的事情。Value
)将在其对应的Even though
WeakReference
does not implementIDisposable
, it does use an unmanaged resource (aGCHandle
). When aWeakReference
is abandoned, it must make certain that resource gets released before theWeakReference
itself gets garbage-collected; if it did not, the system would have no way of knowing that theGCHandle
was no longer needed. To deal with this, aWeakReference
release itsGCHandle
(thus invalidating itself) in itsFinalize
method. If this happens before the execution of aFinalize
method that attempts to use theWeakReference
, the latter method will have no way of getting theWeakReference
's former target.The constructor for
WeakReference
accepts a parameter which indicates whether its target should be invalidated as soon as its target becomes eligible for immediate finalization (parameter valuefalse
), or only when its target becomes eligible for annihilation (parameter valuetrue
). I'm not sure whether that parameter will cause theWeakReference
itself to be resurrected for one GC cycle, but that might be a possibility.Otherwise, if you are using .net 4.0, there is a class called
ConditionalWeakTable
that might be helpful; it allows the lifetimes of various objects to be linked. You could have a finalizable object which holds a strong reference to the object to which you want a weak reference, and have the only reference to that finalizable object be stored in aConditionalWeakTable
, keyed by the object to which the weak reference is desired. The latter object (theValue
of theConditionalWeakTable entry) will become eligible for finalization when its corresponding
Key` does; it could then do something suitable with the strong reference it holds.