Building a Leak-Proof Eventing Model
One of the main features of the .NET Framework is to provide automatic memory management via garbage collection. You might think this would solve the problem of memory leaks in managed applications, but the effectiveness of the garbage collector can be hampered by your code. The GC will not release memory from objects that have root references, which can be local or static variables. If you maintain these references, the GC will not collect them. If you create more instances and keep references to them, memory consumption will continue to grow, resulting in a leaky application.
Note: You can download the code for this blog post here.
Sometimes it’s not so apparent exactly where you’re holding a reference. This is the case with delegates, which are basically type-safe function pointers. If you fire up Reflector and take a look at a delegate definition, such as Action or EventHandler, you’ll see that the constructor takes two parameters. The second is a native integer that basically points to the memory address of the target method. But the first parameter is a System.Object pointing to the instance that owns the target method. (It’s set to null when pointing to a static method.)
public class System.EventHandler : Delegate
{
public EventHandler(object @object, IntPtr method) { } }
In other words, a reference to the delegate is actually a reference to the instance where the target method is defined. If an event publisher holds the delegate reference, then you can’t simply set the subscriber instance to null and expect it to be garbage collected. The best thing would be for the subscriber to unregister from the event before it gets disposed. But that means having to implement IDisposable or, worse yet, implementing a finalizer. This is error-prone and far from fool-proof. Trust me, you don’t want to go there.
What we need is a way for a publisher to expose an event while allowing subscribers to be garbage collected. Enter the WeakReference, which is a reference that won’t prevent the referenced object from being garbage collected. A publisher can weakly reference an event subscriber and only fire the event if the subscriber has not been garbage collected, which it can verify by checking the IsAlive property of the weak reference.
The question is, how do you get a reference to the subscriber’s delegate without referencing the delegate? One idea is to have a weak reference to the delegate itself. But this won’t work because there’s nothing to prevent the delegate from being garbage collected, even if the subscriber it points to is still alive.
If the publisher weakly references the delegate and the delegate gets garbage collected, there’s no way for the publisher to fire the event. If the publisher weakly references the subscriber, but maintains a hard reference to the delegate, the subscriber won’t be garbage collected when it needs to be, resulting in a potential memory leak.
One solution is for the publisher to weakly reference the subscriber and re-create the delegate when it needs to fire the event. That way, the subscriber can be collected, because the publisher does not reference the delegate, but it can fire the target method by getting a pointer to it only if the subscriber is still alive.
Here is what that code would look like:
// Note: Don’t use this code with Silverlight
class WeakDelegate
{
private readonly WeakReference subscriber;
private readonly string methodName;
private readonly Type delegateType;
public WeakDelegate(Delegate targetMethod)
{
this.subscriber = new WeakReference(targetMethod.Target);
this.methodName = targetMethod.Method.Name;
this.delegateType = targetMethod.GetType();
}
public bool IsAlive
{
get { return subscriber != null && subscriber.IsAlive; }
}
public Delegate GetDelegate()
{
if (IsAlive)
{
return Delegate.CreateDelegate(delegateType,
subscriber.Target, methodName);
}
return null;
}
}
In full .NET (and consequently WPF) this code works like a charm. However, if you were to try it on Silverlight or Windows Phone, you’ll soon be banging your head against a wall (at least figuratively). Those flavors of .NET put some restrictions on Delegate.CreateDelegate. First of all, the target method needs to be public or you’ll get a MethodAccessException. It has to be of form Action or Action<T> or you’ll get a MissingMethodException. It has to be an instance method or you’ll get either a MissingMethodException or an ArgumentException. Yikes!
While it may seem like we’re between a rock and a hard place, there is a solution. The key is for the publisher to use a proxy to fire events on its behalf. The proxy, which has delegates referencing the subscriber, is referenced weakly by the publisher and strongly by the subscriber. The subscriber can be garbage collected because the delegate pointing to it is only referenced by the subscriber itself. Because the subscriber has a reference to the publisher proxy, the delegate won’t be garbage collected until the subscriber is. Best of all, this approach works across the board with full .NET / WPF, Silverlight and Windows Phone. Yay!
So what does the code look like for this pattern? First, we will need an interface that contains events that the subscriber can subscribe to. We’ll call it IPublisher.
interface IPublisher
{
event EventHandler SomeEvent;
}
Notice we’re exposing an event of type EventHandler. This is just an example. You can expose any number of events that suit your fancy, including various forms of EventHandler<T>. For illustration purposes I’m trying to keep it simple. The subscriber accepts an IPublisher to its constructor so that it can subscribe to the event as it normally would.
class Subscriber
{
IPublisher publisher;
public Subscriber(IPublisher publisher)
{
this.publisher = publisher;
publisher.SomeEvent += OnSomeEvent;
}
private void OnSomeEvent(object sender, EventArgs e)
{
Console.WriteLine('SomeEvent was fired');
}
}
There’s nothing unusual here. Now for the publisher proxy. For that we’ll need another interface: IPublisherProxy, which extends IPublisher by adding a method that fires the event. If you added more events to IPublisher, you would add corresponding methods to IPublisherProxy to fire those events.
interface IPublisherProxy : IPublisher
{
void FireEvent(object sender, EventArgs e);
}
The subscriber needs to expose an IPublisherProxy to the publisher, so that it can weakly reference it and also call FireEvent. For that we need yet another interface that has a property of type IPublisherProxy. We’ll call it ISubscriber.
interface ISubscriber
{
IPublisherProxy PublisherProxy { get; }
}
The subscriber will need to implement this interface and maintain a hard reference to the publisher proxy so that it will only be garbage collected when the subscriber is GC’d. Implementing it explicitly will be cleaner because only the publisher is interested in it.
class LeakProofSubscriber : ISubscriber
{
IPublisher publisher = null;
public LeakProofSubscriber(IPublisher publisher)
{
// Subscribe to events
this.publisher = publisher;
this.publisher.SomeEvent += OnSomeEvent;
}
// Explicitly implement IPublisherProxy IPublisherProxy publisherProxy = new PublisherProxy(); IPublisherProxy ISubscriber.PublisherProxy { get { return publisherProxy; } } private void OnSomeEvent(object sender, EventArgs e)
{
Console.WriteLine('SomeEvent was fired');
}
}
Now the fun begins: building a leak-proof publisher. First we’ll need a List<WeakReference> with weakly referenced publisher proxies. Then, in order to add proxies, we implement the event manually by providing our own add and remove constructs. The compiler simply translates these to Add and Remove methods that accept a delegate. We’ll pull out the subscriber from the Target property of the delegate, cast it to ISubscriber, read the PublisherProxy property, then add or remove the incoming delegate to its event. Lastly, we’ll add or remove the proxy from our list of weak references. When we want to fire the event, we simply check to see if the proxy is alive and then call FireEvent.
class LeakProofPublisher : IPublisher
{
// List of weakly referenced proxies
private List<WeakReference> weakProxies
= new List<WeakReference>();
// Subscribers to add and remove themselves from subscriptions
public event EventHandler SomeEvent
{
add
{
// Get subscriber
ISubscriber subscriber = value.Target as ISubscriber;
if (subscriber != null)
{
// Add handler to proxy
IPublisherProxy proxy = subscriber.PublisherProxy;
proxy.SomeEvent += value;
// Add to list of weakly referenced proxies
weakProxies.Add(new WeakReference(proxy));
}
}
remove
{
// Get subscriber
ISubscriber subscriber = value.Target as ISubscriber;
if (subscriber != null)
{
// Remove handler from proxy
IPublisherProxy proxy = subscriber.PublisherProxy;
proxy.SomeEvent -= value;
// Remove from list of weakly referenced proxies
for (int i = weakProxies.Count - 1; i >= 0; i--)
{
var weakProxy = weakProxies[i];
var existing = GetProxy(weakProxy);
if (existing != null &&
object.ReferenceEquals(existing, proxy))
weakProxies.Remove(weakProxy);
}
}
}
}
// Ask proxy to fire event
public void FireEvent()
{
foreach (var weakProxy in weakProxies)
{
var proxy = GetProxy(weakProxy);
if (proxy != null)
{
proxy.FireEvent(this, EventArgs.Empty);
}
}
}
private IPublisherProxy GetProxy(WeakReference weakProxy)
{
if (weakProxy != null && weakProxy.IsAlive)
{
IPublisherProxy proxy = weakProxy.Target
as IPublisherProxy;
if (proxy != null)
{
return proxy;
}
}
return null;
}
}
There you go! I didn’t say the solution would be easy, but the problem is solvable. The nice part is that, while the publisher does the heavy lifting, the only extra code that the subscriber needs is the PublisherProxy property. However, because we implement it explicitly, it doesn’t pollute the subscriber’s appearance. You can download the full source code here.
Although I wouldn’t recommend making all your events leak-proof in this manner, there are scenarios where making events leak-proof is an essential requirement. An example would be a message bus (also called an event aggregator or event mediator), which I’ve incorporated into my Simple MVVM Toolkit (version 2 will support leak-proof events and drop the requirement to unregister for messages). You wouldn’t want subscribers to not get garbage collected simply because they registered to received messages and failed to unregister.
As you can see, weak events can be a thorny problem to solve. Hopefully this approach will help if you have the need for them. Happy coding!
Reference: Building a Leak-Proof Eventing Model from our NGC partner Tony Sneed at the Tony Sneed’s Blog blog.