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:
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:
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:
Shawn A. Van Ness- Greg Schechter (real case study; schemas will help you to understand the problem)
- Ian Griffiths and Xavier Musy (extensions and improvements)
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.