views:

72

answers:

4

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.

+1  A: 

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...

Reed Copsey
The former object's reference to the latter object would have to be a WeakReference. Certainly the objects should implement iDisposable, but the whole point of finalizers is to deal with cases where iDisposable should have been called but wasn't. Fixing things when the subscription event fires is a common approach, and a good one, but if the event in question is something like Disposed, it might not happen for an arbitrarily long time.
supercat
@supercat: "...if the event in question is something like Disposed, it might not happen for an arbitrarily long time." This doesn't really matter, if the only goal is to unsubscribe from the event. It can take as long as you want, since it does nothing until the event is fired anyways...
Reed Copsey
If the object holding the subscription will exist for a long time, the subscription will essentially be a memory leak. Probably a small enough one not to matter in most cases, but it's best to quash memory leaks altogether. If the queue-item object holds a StringBuilder, the timer event that cleans up finalized objects could log its value, helping to track down the leak.
supercat
A: 

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.

JMarsch
If the publisher goes out of scope, all the delegates go bye-bye and everything is fine. The problem occurs if the SUBSCRIBER effectively goes out of scope but the publisher will stay in scope for a long time.
supercat
So you are having problems where the publisher is keeping the subscriber alive? (no offense, but it was hard to follow who was the publisher and who was the subscriber in your original post). I had commented here, but instead I'm going to edit my post -- more room that way.
JMarsch
Perhaps I need to add another edit to my original post to make clear that the object holding the subscription would hold a WEAK reference to the object that will be held by anyone interested in the subscription.
supercat
Incidentally, I'd seen the "Weak Events" article on CodeProject and didn't really like any of the solutions; looking back at the article, it seems it did mention the possibility of using a finalizer, but unregistering directly from within a finalizer seems very dangerous. Putting the unregistration into a stack or queue so that it will get done by a non-finalizer thread would seem safer.
supercat
@JMarsch: I've come up with an event-like system using weak references and generic interfaces. Calling raiseAction(of T) would call iDoAction(of T) on all subscribers. Perhaps I'm trying too hard to make a generalized weak-delegate concept, but the approach of putting finalize-notifiers in a pre-allocated queue would seem like it might be good for other reasons as well, such as logging the fact that objects have gotten finalized without being disposed.
supercat
@supercat my weak-event solution was pretty similar to what you describe. With a little bit of tweaking, you can actually make it work with the original c# event syntax (so subscriber does a publisher.event += thedelegate to weakly hook the event). (I ended up not doing it that way though, because I the idea of registering the event differently if it is going to be weakly bound -- different syntax for different semantics. The finalizer logging thing is interesting, but I get really paranoid about having one object messing with other managed objects inside a finalizer.
JMarsch
A: 

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;
            }
        }
    }
}
FMM
@FMM: that method actually won't work (I investigated it a long time ago). The problem is that the only thing holding on to the delegate is a weakref! You can test it by running that code, but also call GC.Collect() to force a collection. The event handlers will just stop running because the delegates have been collected.
JMarsch
btw: I agree about doing everything you can to avoid the situation.
JMarsch
@JMarsch: AHA, you're absolutely right.
FMM
A: 

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.

FMM