ASP.NET Hosting

Events, references, garbage collecting, memory leaks and weak delegates

I was playing with services and containers, as part of my implementation of Inversion of Control. All was fine until events came into play.
I needed to connect two services through events. Oh, all was working fine: there were no apparent troubles. But under the too calm surface sneaked a dreadful memory leak. Events don't play well with a loosely coupled environment by default. Better be warned.

This is related to the so-called "lapsed listener" problem.
Steve Maine has a good description of the problem. I'll copy-paste it here so I don't have to rephrase it:

The “lapsed listener” problem occurs when objects subscribe to events and subsequently fall out of scope. The problem is that the event subscriber doesn’t get garbage collected because the event is still holding a reference to it inside of the event’s invocation list. The event subscriber is still considered reachable from the GC’s point of view. As such, it doesn’t get collected until the event goes out of scope (which is usually at application shutdown) which means that the event subscriber is effectively “leaked”. Moral of the story: when you implement an Observer pattern, it’s important to consider the relative lifetime of events and subscribers. If implemented naively, you’ll end up having objects that live a lot longer than you think they should. Unsubscribe() is your friend.

Here is a small schema representing this:

In fact, .NET's delegates and events are implementations of the Observer Design Pattern. But the current problem is one more reminder that Design Patterns should not be applied blindly.

If you write the following code, you'll see that the object instance gets correctly released and collected:

StoopidObject object = new StoopidObject();
GC.Collect();
GC.WaitForPendingFinalizers();

If you write the following code instead, although there is no apparent reference kept to the Observer, the Observer instance will not be released:

Observer observer = new Observer();
Subject subject = new Subject();
subject.SomethingHappened += new EventHandler(observer.subject_SomethingHappened);
GC.Collect();
GC.WaitForPendingFinalizers();

Guys from around the community came with various solutions. They call them Weak Delegates. Follow the links to learn more:

I chose another way because the proposed solutions rely on weak references, and that is not satisfying in my case. With "weak delegates", the observers continue to receive events while not garbage collected. You never know when garbage collecting happens. If you base your developments on weak references, you have to accept the fact that your objects do not disappear immediately.

I want to prevent the notifications to be received by the observer as soon as it is meant to be disconnected. For that purpose, I ask the observer I'm going to remove to disconnect cleanly from its subjects. For simplicity, I use the Dispose method for this. This is because I know I'll call Dispose() each time I want to get rid of an object, this runs in a specific framework. Another method more explicit to the user could be used instead.

You can take a look at the source code I used for my tests.

Update: I show how to force disconnection in another post.
Update: I cover the same subject and much more in an article.

5 Comments

  • It will be great if you can explore on your code here.

  • I show how to force disconnection in another post: http://weblogs.asp.net/fmarguerie/archive/2009/09/09/forcing-event-unsubscription.aspx

  • I cover the same subject and much more in an article: http://weblogs.asp.net/fmarguerie/archive/2009/11/03/article-detect-avoid-memory-leaks.aspx

  • Hi,

    Interesting article. I see from your example that when the observers are disconnected and garbage collection is called, the observers cleanly get unsubscribed without much fuss.

    However I also have noticed that on calling GC.Collect() all the weak delegates' property wr.IsAlive becomes false even though the array list _Observers is not empty. What would be the point of that?

  • shake, I guess that you see that behavior with the SAVanNess sample. This was more than five years ago, and I don't remember why I included this example from Shawn A. Van Ness.
    All I know is that this code is not good at all! We don't want the connections to disappear when the GC performs a collection. Especially because the observers are still alive, as you mention.
    I'd say that only my example (Fabrice) is correct.

Comments have been disabled for this content.