繁体   English   中英

终结者访问托管的东西

[英]Finalizers accessing managed stuff

我很清楚终结器通常用于控制非托管资源。 在什么情况下终结者可以处理受管理的人?

我的理解是,在终结器队列中的存在将阻止任何对象或由此强烈引用的对象被收集,但它(当然)不会保护它们不被最终化。 在正常的事件过程中,一旦对象被最终确定,它将从队列中移除,并且它引用的任何对象将不再受到下一次GC传递的收集保护。 在调用终结器时,可能已经为对象引用的任何对象组合调用终结器; 一个人不能依赖于在任何特定序列中被调用的终结器,但是对象引用一个保持应该仍然有效。

很明显,终结器必须永远不会获取锁,也不能尝试创建新对象。 但是,假设我有一个订阅某些事件的对象,以及另一个实际使用这些事件的对象。 如果后一个对象符合垃圾收集条件,我希望让前一个对象尽快取消订阅事件。 请注意,在任何活动对象不保留任何订阅之前,前一个对象永远不会有资格进行最终确定。

拥有一个无锁的链表列表或需要取消订阅的对象队列是否可行,并让主对象的终结器对堆栈/队列中的另一个对象进行引用? 创建主对象时必须分配链表项对象(因为终止器中的分配将被禁止),并且可能需要使用类似计时器事件的事件来轮询队列(因为事件取消订阅)必须在终结器线程之外运行,并且有一个线程的唯一目的是等待终结器队列上出现的东西可能是愚蠢的,但是如果终结器可以安全地引用它的预分配链表列表对象和与其类相关联的主队列对象,它可以允许在15秒左右的时间内取消订阅完成。

这是个好主意吗? (注意:我正在使用.net 2.0;此外,尝试添加到堆栈或队列可能会在Threading.Interlocked.CompareExchange上旋转几次,但我不希望它应该被卡住很长时间)。

编辑

当然,订阅事件的任何代码都应该实现iDisposable,但是一次性事物并不总是正确处理。 如果有,则不需要终结器。

我关注的场景如下所示:实现iEnumerator(T)的类挂钩到其关联类的changeNotify事件,以便在基础类发生更改时可以合理地处理枚举(是的,我知道Microsoft认为所有枚举器应该放弃,但有时一个可以继续工作的普查员会更有用)。 很可能在几天或几周内,类的实例可能被枚举数千甚至数百万次,但在此期间根本不会更新。

理想情况下,枚举器永远不会被遗忘而不会被丢弃,但是枚举器有时用于“foreach”和“using”不适用的上下文中(例如,某些枚举器支持嵌套枚举)。 精心设计的终结器可能允许一种方法来处理这种情况。

顺便说一句,我要求任何应该继续通过更新的枚举必须使用通用的IEnumerable(T); 如果集合被修改,那么不处理iDisposable的非泛型形式将不得不抛出异常。

但是,假设我有一个订阅某些事件的对象,以及另一个实际使用这些事件的对象。 如果后一个对象符合垃圾收集条件,我希望让前一个对象尽快取消订阅事件。 请注意,在任何活动对象不保留任何订阅之前,前一个对象永远不会有资格进行最终确定。

如果“后一个对象”是使用事件的那个,并且“前”对象是订阅事件的对象,则“前”对象必须有某种方式将事件信息传递给“后者”对象 - 意思是它会对“后者”有一些参考。 机会是,这将使“后者”对象永远不会成为候选人。


话虽这么说,我建议通过终结器避免这种类型的托管资源释放,除非绝对必要。 您描述的架构看起来非常脆弱,并且非常难以实现。 这可能是IDisposable的一个更好的候选者,终结者是“最后的沟渠”清理工作。

虽然IDisposable通常是关于释放本机资源 - 但它可以是释放任何资源,包括您的订阅信息。

此外,我试图避免使用单个全局对象引用集合 - 让您的对象在内部使用WeakReference可能更有意义。 收集“后一个”对象后,“前”对象的WeakReference将不再有效。 下次引发事件订阅时,如果内部WeakReference不再有效,您可以取消订阅自己。 不需要全局队列,列表等 - 它应该正常工作......

让我确保理解 - 您是否担心仍然订阅收集的活动发布者的活动订阅者的泄密?

如果是这样,那么我认为你不必担心它。

这就是我的意思,假设“前”对象是事件订阅者,而“后者”对象是事件发布者(引发事件):

订阅者(前者)“订阅”的唯一原因是您创建了一个委托对象并将该委托传递给发布者(“后者”)。

如果查看委托成员,它会引用订阅者对象以及将要执行的订阅者上的方法。 所以有一个如下所示的引用链:publisher - > delegate - > subscriber(发布者引用委托,引用订阅者)。 它是单向链 - 订户不持有委托的引用。

因此,保留委托的唯一根源是发布者(“后者”)。 当后者有资格获得GC时,代表也是如此。 除非您的订阅者在取消订阅时需要采取某些特殊操作,否则当代表被收集时,它们将有效地取消订阅 - 没有泄漏)。

编辑

基于supercat的评论,听起来问题是发布者保持订户活着。

如果这是问题,那么终结者将无法帮助您。 原因:您的发布商对您的订阅者(通过代理人)提供了真实的真实参考,并且发布者已植根(否则它将符合GC的条件),因此您的订阅者已植根,并且无法完成最终确定或GC。

如果您在发布商保持订户活跃方面遇到问题,我建议您搜索弱参考事件。 以下是一些可以帮助您入门的链接: http//www.codeproject.com/KB/cs/WeakEvents.aspx http://www.codeproject.com/KB/architecture/observable_property_patte.aspx

我也必须处理这个问题。 大多数有效模式涉及更改发布者,以便它对代表持有弱引用。 然后你有一个新的问题 - 委托没有根,你就会以某种方式让它保持活力。 上面的文章可能会做那样的事情。 一些技术使用反射。

我曾经使用过一种不依赖于反射的技术。 但是,它要求您能够对发布者和订阅者中的代码进行更改。 如果您想查看该解决方案的示例,请与我们联系。

我打算将对象称为“发布者”和“订阅者”,并重申我对该问题的理解:

在C#中,发布者将(有效地)保持对订阅者的引用,从而防止订阅者被垃圾回收。 我可以做什么才能在不明确管理订阅的情况下对订阅者对象进行垃圾回收?

首先,我建议尽我所能避免这种情况。 现在,我将继续前进并假设你有,考虑到你发布的问题无论如何=)

接下来,我建议挂钩添加和删除发布者事件的访问者并使用WeakReferences集合。 然后,只要调用事件,您就可以自动取消挂起这些订阅。 这是一个非常粗略,未经测试的例子:

private List<WeakReference> _eventRefs = new List<WeakReference>();

public event EventHandler SomeEvent
{
    add
    {
        _eventRefs.Add(new WeakReference(value));
    }
    remove
    {
        for (int i = 0; i < _eventRefs; i++)
        {
            var wRef = _eventRefs[i];
            if (!wRef.IsAlive)
            {
                _eventRefs.RemoveAt(i);
                i--;
                continue;
            }

            var handler = wRef.Target as EventHandler;
            if (object.ReferenceEquals(handler, value))
            {
                _eventRefs.RemoveAt(i);
                i--;
                continue;
            }
        }
    }
}

让我们再试一次。 您可以将您的事件处理程序添加到您的发布者,如下所示:

var pub = new Publisher();
var sub = new Subscriber();
var ref = new WeakReference(sub);

EventHandler handler = null; // gotta do this for self-referencing anonymous delegate

handler = (o,e) =>
{
    if(!ref.IsAlive)
    {
        pub.SomeEvent -= handler; // note the self-reference here, see comment above
        return;
    }


    ((Subscriber)ref.Target).DoHandleEvent();
};

pub.SomeEvent += handler;

这样,您的代理人不会直接引用订阅者,并且无论何时收集订阅者都会自动取消挂钩。 您可以将其实现为Subscriber类的私有静态成员(出于封装的目的),只需确保它是静态的,以防止无意中持有对“this”对象的直接引用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM