【发布时间】:2014-09-08 18:12:14
【问题描述】:
在我参与的一个项目中,大量使用了WeakAction。这是一个允许保持对动作实例的引用而不导致其目标不被垃圾收集的类。它的工作方式很简单,它对构造函数执行一个操作,并保持对操作目标和方法的弱引用,但丢弃对操作本身的引用。到了执行动作的时候,它会检查目标是否还活着,如果是,就调用目标上的方法。
除了一种情况 - 当动作在闭包中实例化时,一切都很好。考虑以下示例:
public class A
{
WeakAction action = null;
private void _register(string msg)
{
action = new WeakAction(() =>
{
MessageBox.Show(msg);
}
}
}
由于 lambda 表达式使用 msg 局部变量,C# 编译器会自动生成一个嵌套类来保存所有闭包变量。动作的目标是嵌套类的实例而不是 A 的实例。一旦构造函数完成,传递给 WeakAction 构造函数的动作就不会被引用,因此垃圾收集器可以立即处理它。稍后,如果WeakAction 被执行,它将无法工作,因为目标不再是活动的,即使A 的原始实例是活动的。
现在我不能改变WeakAction 的调用方式(因为它被广泛使用),但我可以改变它的实现。我正在考虑尝试找到一种方法来访问 A 的实例并强制嵌套类的实例在 A 的实例仍然存在时保持活跃,但我不知道如何获取它。
有很多关于A 与任何事情的关系的问题,并且建议改变A 创建弱动作的方式(我们不能这样做)所以这里是一个澄清:
A 类的实例希望B 类的实例在发生某事时通知它,因此它使用Action 对象提供回调。 A 不知道B 使用弱动作,它只是提供了一个Action 作为回调。 B 使用 WeakAction 的事实是未公开的实现细节。 B 需要存储这个动作并在需要时使用它。但是B 的寿命可能比A 长得多,并且持有对正常Action 的强引用(它本身持有生成它的A 实例的强引用)导致A 永远不会被垃圾收集.如果A 是不再存在的项目列表的一部分,我们希望A 被垃圾回收,并且因为B 持有Action 的引用,它本身指向A ,我们有内存泄漏。
所以B 不是持有A 提供的Action,而是B 将其包装成WeakAction 并仅存储弱操作。到了调用它的时候,B 只有在WeakAction 还活着的情况下才会这样做,只要A 还活着就应该如此。
A 在方法中创建该操作,并且不会自行保留对它的引用 - 这是给定的。由于Action 是在A 的特定实例的上下文中构造的,因此该实例是A 的目标,当A 死亡时,对它的所有弱引用变为null 所以B 知道不要调用它并处理 WeakAction 对象。
但有时生成Action 的方法会使用在该函数中本地定义的变量。在这种情况下,动作运行的上下文不仅包括A 的实例,还包括方法内部的局部变量的状态(称为“闭包”)。 C# 编译器通过创建一个隐藏的嵌套类来保存这些变量(我们称之为A__closure)来做到这一点,而成为Action 目标的实例是A__closure 的实例,而不是A 的实例。这是用户不应该意识到的事情。除了A__closure 的这个实例只被Action 对象引用。而且由于我们创建了对目标的弱引用,并且不持有对动作的引用,因此没有对 A__closure 实例的引用,垃圾收集器可能(并且通常会)立即处理它。所以A 活着,A__closure 死了,尽管A 仍然期待回调被调用,B 做不到。
这就是错误。
我的问题是,如果有人知道 WeakAction 构造函数(实际保存原始 Action 对象的唯一一段代码)可以以某种神奇的方式从 @ 中提取 A 的原始实例。在Action 的Target 中找到的987654377@ 实例。如果是这样,我或许可以延长 A__Closure 的生命周期以匹配 A 的生命周期。
【问题讨论】:
-
在 C# 的更高版本中是否已修复此问题?
标签: c# weak-references