终结器访问托管内容

发布于 2024-09-11 10:39:39 字数 1216 浏览 4 评论 0原文

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

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

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

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

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

编辑

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

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

理想情况下,枚举数在不被释放的情况下永远不会被忘记,但枚举数有时用在“foreach”和“using”不适用的上下文中(例如,某些枚举数支持嵌套枚举)。精心设计的终结器可能会提供一种方法来处理这种情况。

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

I am well aware that finalizers are typically used to control unmanaged resources. Under what circumstances may a finalizer deal with managed ones?

My understanding is that presence in the finalizer queue will prevent any object, or objects strongly referenced thereby, from being collected, but it will not (of course) protect them from finalization. In the normal course of events, once an object is finalized it will be removed from the queue and any objects it references will no longer be protected from collection on the next GC pass. By the time a finalizer is called, the finalizers may have been called for any combination of objects referred to by the object; one cannot rely upon finalizers being called in any particular sequence, but the object references one holds should still be valid.

It's pretty clear that a finalizer must never acquire locks, nor attempt to create a new object. Suppose, however, that I have an object that subscribes to some events, and another object which actually uses the events. If the latter object becomes eligible for garbage collection I want to have the former object unsubscribe from events as soon as practical. Note that the former object will never become eligible for finalization until no subscriptions for it are held by any live object.

Would it be practical to have a lock-free linked-list stack or queue of objects which needed to be unsubscribed, and have the main object's finalizer put a reference to the other object on the stack/queue? The linked-list item object would have to be allocated when the main object was created (since allocation within the finalizer would be forbidden), and it would probably be necessary to use something like a timer event to poll the queue (since the event unsubscription would have to run outside the finalizer thread, and it would probably be silly to have a thread whose sole purpose was to wait for something to appear on the finalizer queue), but if the finalizer could safely reference its pre-allocated linked-list object and the main queue object associated with its class, it could allow the events to be unsubscribed within 15 seconds or so of finalization.

Would that be a good idea? (Notes: I'm using .net 2.0; also, an attempt to add to the stack or queue might spin a few times on Threading.Interlocked.CompareExchange, but I wouldn't expect that it should ever be stuck very long).

EDIT

Certainly any code which subscribes events should implement iDisposable, but disposable things aren't always disposed properly. If there were, there wouldn't be any need for finalizers.

My scenario of concern would be something like the following: a class implementing iEnumerator(of T) hooks onto a changeNotify event of its associated class so that an enumeration can be sensibly handled if the underlying class changes (yes, I know Microsoft thinks all enumerators should simply give up, but sometimes an enumerator which can keep working will be more useful). It's quite possible that an instance of the class might be enumerated many thousands or even millions of times over the course of days or weeks, but not be updated at all during that time.

Ideally, the enumerator would never be forgotten about without being disposed, but enumerators are sometimes used in contexts where "foreach" and "using" aren't applicable (e.g. some enumerators support nested enumeration). A carefully-designed finalizer might allow a means to deal with this scenario.

Incidentally, I'd require that any enumeration which is supposed to continue through updates must use the generic IEnumerable(of T); the non-generic form, which doesn't handle iDisposable, would have to throw an exception if the collection gets modified.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

瞳孔里扚悲伤 2024-09-18 10:39:39

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

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


话虽这么说,我建议避免通过终结器进行这种类型的托管资源释放,除非绝对必要。您所描述的架构似乎非常脆弱,而且很难正确解决。这可能是 IDisposable 的更好候选者,终结器是“最后的努力”清理工作。

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

另外,我会尽量避免使用单个全局对象引用集合 - 让您的对象在内部仅使用 弱引用。一旦“后一个”对象被收集,“前一个”对象的 WeakReference 将不再有效。下次引发事件订阅时,如果内部 WeakReference 不再有效,您可以自行取消订阅。不需要全局队列、列表等 - 它应该可以工作......

Suppose, however, that I have an object that subscribes to some events, and another object which actually uses the events. If the latter object becomes eligible for garbage collection I want to have the former object unsubscribe from events as soon as practical. Note that the former object will never become eligible for finalization until no subscriptions for it are held by any live object.

If the "latter object" is the one that's using the events, and the "former" object is the one subscribing to the events, the "former" object has to have some way to pass the event info to the "latter" object - meaning it's going to have some reference in place to "latter". Chances are, this will keep the "latter" object from ever being a GC candidate.


That being said, I would recommend avoid this type of managed resource deallocation via the finalizer, unless absolutely necessary. The architecture you're describing seems very fragile, and very tricky to get right. This is probably a better candidate for IDisposable, with the finalizer being the "last ditch" cleanup effort.

Although IDisposable is typically about releasing native resources - it can be about releasing any resource, including your subscription information.

Also, I'd try to avoid having a single global collection of object references - it might make more sense to have your objects internally just use a WeakReference. As soon as the "latter" object is collected, the "former" object's WeakReference would no longer be valid. The next time an event subscription is raised, if the internal WeakReference is no longer valid, you can just unsubscribe yourself. No need for global queues, lists, etc - it should just work...

这个俗人 2024-09-18 10:39:39

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

在 C# 中,发布者将(有效)保存对订阅者的引用,从而防止订阅者被垃圾收集。我该怎么做才能在不显式管理订阅的情况下对订阅者对象进行垃圾收集?

首先,我建议您尽我所能从一开始就避免这种情况。现在,我将继续并假设您已经这样做了,考虑到您无论如何都要发布问题 =)

接下来,我建议挂钩发布者事件的添加和删除访问器并使用 Wea​​kReferences 集合。然后,只要调用该事件,您就可以自动取消这些订阅。这是一个极其粗糙、未经测试的示例:

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;
            }
        }
    }
}

I'm going to call the objects "publisher" and "subscriber" and restate my understanding of the problem:

In C#, the publisher will (effectively) hold references to the subscribers, preventing subscribers from being garbage collected. What can I do so that the subscriber objects can be garbage collected without explicitly managing the subscriptions?

First, I would recommend doing everything I could to avoid this situation in the first place. Now, I'm going to move on and assume you have, considering you're posting the question anyway =)

Next, I would recommend hooking the add and remove accessors of the publisher's event(s) and using a collection of WeakReferences. You can then automatically unhook those subscriptions whenever the event is invoked. Here's an extremely rough, untested example:

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;
            }
        }
    }
}
绳情 2024-09-18 10:39:39

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

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

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

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

如果您查看委托成员,它会引用订阅者对象以及订阅者上将要执行的方法。所以有一个参考链,看起来像这样:publisher -->代表-->订阅者(发布者引用委托,委托引用订阅者)。这是一个单向链——订阅者不持有对委托的引用。

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

编辑

根据超级猫的评论,听起来问题在于发布者让订阅者保持活跃。

如果这就是问题所在,那么终结器将无法帮助您。原因:您的发布商对您的订阅者有真实、善意的引用(通过委托),并且发布者已获得 root 权限(否则将有资格获得 GC),因此您的订阅者也已获得 root 资格,并且没有资格获得最终确定或 GC。

如果您在发布者保持订阅者活动方面遇到困难,我建议您搜索弱引用事件。以下是一些可帮助您入门的链接: http://www.codeproject.com/ KB/cs/WeakEvents.aspx http://www.codeproject.com /KB/architecture/observable_property_patte.aspx

我也曾经不得不处理过这个问题。大多数有效的模式都涉及更改发布者,以便它对委托持有弱引用。然后你就会遇到一个新问题——委托没有扎根,你必须以某种方式让它保持活力。上面的文章可能做了类似的事情。有些技术使用反射。

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

Let me make sure I understand -- are you worried about leaks from event subscribers that remain subscribed to a collected event publisher?

If that's the case, then I don't think you have to worry about it.

Here's what I mean assuming that the "former" object is the event subscriber and the "latter" object is the event publisher (raises the event):

The only reason that the subscriber (former) is "subscribed" is because you created a delegate object and passed that delegate to the publisher ("latter").

If you look at the delegate members, it has a reference to the subscriber object and to the method on the subscriber that will be executed. So there is a reference chain that looks like this: publisher --> delegate --> subscriber (publisher references delegate, which references subscriber). It's a 1-way chain -- the subscriber does not hold a reference to delegate.

So, the only root that keeps the delegate around is on the publisher ("latter"). When latter becomes eligible for GC, so does the delegate. Unless there is some special action you want for your subscribers to take when they unsubscribe, they will effectively become unsubscribed when the delegate gets collected -- there is no leak).

Edit

Based on supercat's comments, it sounds like the problem is that the publisher is keeping the subscriber alive.

If that's the problem, then finalizers won't help you. Reason: Your publisher has a real, bonafide reference to your subscriber (via the delegate), and the publisher is rooted (otherise it would be eligible for GC), so your subscribers are rooted, and will not be eligible for finalization or GC.

If you are having trouble with publisher keeping subscriber alive, I would suggest that you search for weak-ref events. Here are a couple links to get you started: http://www.codeproject.com/KB/cs/WeakEvents.aspx http://www.codeproject.com/KB/architecture/observable_property_patte.aspx.

I had to deal with this once as well. Most of the effective patterns involve changing the publisher so that it holds a weak-ref to the delegate. Then you have a new problem -- the delegate isn't rooted, and you hvae to keep it alive somehow. The articles above probably do something like that. Some techniques use reflection.

I used a technique once that did not rely upon reflection. It required that you be able to make changes to the code in both the publisher and the subscriber, though. If you would like to see a sample of that solution, let me know.

嗼ふ静 2024-09-18 10:39:39

让我们再试一次。您可以像这样将事件处理程序添加到发布者中吗:

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”对象的直接引用。

Let's try this again. Can you add your event handlers to your publisher like this:

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;

This way, your delegate doesn't keep a direct reference to the subscriber, and automatically unhooks itself whenever the subscriber gets collected. You could implement this as a private static member of the Subscriber class (for the purposes of encapsulation), just make sure it's static to prevent inadvertently holding onto a direct reference to the "this" object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文