Based on a recent question from Brad, I decided to write up a little post on the topic of event handlers. Specifically, that objects registered to receive events are rooted, meaning that they won't be eligible for GC even if you drop them on the floor. Through some trickery with weak references, you can work around this (if you need to).
So, what's the issue?
Once you register an object as a sink for events in C#, the source object generating the events will keep a reference to it. When the source becomes unreachable, your sink will also become unreachable (assuming that's the only live reference to the sink). An alternative way to make your sink unreachable is to manually unregister it, asking that the source give up its reference. But this means that until either one of these things happen, your object won't be eligible for garbage collection. This probably won’t be surprising for most folks, but for finalizable objects which own resources it does mean you need to be very careful to unregister your sink when it needs to die. Even for objects which don't hold on to such resources, you might be building up a list of unused objects after a while, akin to stashing away references in a collection and forgetting to prune it when you're done.
For example, given source and sink set of types:
class Source { public event EventHandler<EventArgs> Tick; public void OnTick() { EventHandler<EventArgs> t = Tick; if (t != null) t(this, new EventArgs()); } } interface ISink { void HandleTick(object sender, EventArgs e); }
class Source
{
public event EventHandler<EventArgs> Tick;
public void OnTick()
EventHandler<EventArgs> t = Tick;
if (t != null)
t(this, new EventArgs());
}
interface ISink
void HandleTick(object sender, EventArgs e);
And client code which defines a sink implementation:
class MySink : ISink, IDisposable { private IntPtr someHandle; static int counter; int id = counter++; public MySink() { // create someHandle } ~MySink() { // clean up someHandle Console.WriteLine("!{0} finalized", id); } public void Dispose() { // clean up someHandle GC.SuppressFinalize(this); Console.WriteLine("~{0} disposed", id); } public void HandleTick(object sender, EventArgs e) { // use someHandle Console.WriteLine("{0}: Received", id); } }
class MySink : ISink, IDisposable
private IntPtr someHandle;
static int counter;
int id = counter++;
public MySink()
// create someHandle
~MySink()
// clean up someHandle
Console.WriteLine("!{0} finalized", id);
public void Dispose()
GC.SuppressFinalize(this);
Console.WriteLine("~{0} disposed", id);
public void HandleTick(object sender, EventArgs e)
// use someHandle
Console.WriteLine("{0}: Received", id);
The owner of an instance of MySink might not realize what consequences registering the sink for messages will have, namely that it will cause it to become rooted. For example, this simple code does the trick:
Source src = /*...*/; MySink sk = new MySink(); src.Tick += sk.HandleTick;
Source src = /*...*/;
MySink sk = new MySink();
src.Tick += sk.HandleTick;
Given that this type owns unmanaged resources, the user really needs to remember to unregister it when they’re done with it.
And this is a problem because…?
This actually seems like it's the desired/intended behavior for somebody registering for events in most cases. Frequently, a sink object will be created, registered for an event, and then its reference dropped and left on its own to process the incoming messages. I.e. its only purpose in life is to respond to events. It might seem surprising that somebody would register an object and forget to ever unregister it, but I suspect this happens all the time. This is one of those subtle bugs which could manifest over time in a long running application (e.g. a web app), where unmanaged memory just continues to rise and rise until ASP.NET recycles itself. Especially if there’s some AppDomain-wide event registration and routing mechanism.
So, for those situations where we don’t want the event source to keep us alive, what are we to do?
Enter “Weak Event Handlers”
We have this nifty feature called weak references (exposed by the WeakReference class), enabling you to hold on to a reference without keeping it pinned from GC collection. If there are no strong references to an object and the GC runs, it'll sweep these up. This means that the underlying object referred to by the weak reference won't be valid any longer. (For those from the Java camp, be careful—one of the first mistakes I made when moving to the CLR was thinking that weak references == soft references. Soft references are only collected as a last resort, making them perfect for caching. Weak references are always collected if no strong references exist at the time of a GC, so you might have to think carefully about using them. This is one great reason to avoid GC.Collect() in production applications (among many others).)
We can go ahead and use this feature to wrap up our event handler delegates. Then we just hand the wrapper instead of the raw delegate off to event registration, thus preventing the event source from keeping our object alive. So, let’s get started.
First we introduce a general purpose WeakEventHandler<T> class:
class WeakEventHandler<T> where T : EventArgs { private WeakReference weakRef; private EventHandler<T> handler; public WeakEventHandler(EventHandler<T> d) { weakRef = new WeakReference(d); handler = new EventHandler<T>(Invoke); } public EventHandler<T> Handler { get { return handler; } } private void Invoke(object sender, T e) { EventHandler<T> d = (EventHandler<T>)weakRef.Target; if (d != null) d(sender, e); else // hmm! what to do here? } }
class WeakEventHandler<T> where T : EventArgs
private WeakReference weakRef;
private EventHandler<T> handler;
public WeakEventHandler(EventHandler<T> d)
weakRef = new WeakReference(d);
handler = new EventHandler<T>(Invoke);
public EventHandler<T> Handler
get { return handler; }
private void Invoke(object sender, T e)
EventHandler<T> d = (EventHandler<T>)weakRef.Target;
if (d != null)
d(sender, e);
else
// hmm! what to do here?
So now somebody can construct a weak event handler, and register that instead. Once the target of the delegate loses its roots, it’s eligible for GC. For example, the three line snippet from above becomes:
Source src = /*...*/; MySink sk = new MySink(); src.Tick += new WeakEventHandler<EventArgs>(sk.HandleTick).Handler;
src.Tick += new WeakEventHandler<EventArgs>(sk.HandleTick).Handler;
(Note that I initially wanted to subclass Delegate to make a WeakDelegate type, and simply override the internal storage in order to make a general purpose “weak delegate.” Unfortunately, Delegate and MulticastDelegate are treated specially by the CLI (see Partition II), meaning that I couldn’t do this straightforwardly. I’m going to look into it further, but I’m not sure exactly what this would entail.)
But now won't my WeakEventHandler stay alive?
Yep, it does. This is admittedly leaps and bounds better than keeping a finalizable object rooted, but we can probably clean things up a little. Notice the “// hmm! what to do here?” line above?—we can actually put some code in here that intelligently unregisters the weak handler from the source. It requires a bit of generalization, but here’s a shot at it. (Note that this approach requires an event to be triggered to initiate the cleanup, so for events with particularly long delays in between instances, this might not be a great solution.)
First, let’s update our WeakEventHandler<T> class to accept an unregister delegate. This will be invoked in cases where our underlying object has been collected, and hence the weak wrapper should be removed from the event source’s registration list; from this point forward, our previous code just ignores all future requests to fire the event anyhow:
delegate void UnregisterHandler<T>(WeakEventHandler<T> handler) where T : EventArgs; class WeakEventHandler<T> where T : EventArgs { private WeakReference weakRef; private EventHandler<T> handler; private UnregisterHandler<T> unregister; public WeakEventHandler(EventHandler<T> d) : this(d, null) { } public WeakEventHandler(EventHandler<T> d, UnregisterHandler<T> u) { weakRef = new WeakReference(d); handler = new EventHandler<T>(Invoke); unregister = u; } public EventHandler<T> Handler { get { return handler; } } private void Invoke(object sender, T e) { EventHandler<T> d = (EventHandler<T>)weakRef.Target; if (d != null) d(sender, e); else if (unregister != null) unregister(this); } }
delegate void UnregisterHandler<T>(WeakEventHandler<T> handler) where T : EventArgs;
private UnregisterHandler<T> unregister;
public WeakEventHandler(EventHandler<T> d) : this(d, null)
public WeakEventHandler(EventHandler<T> d, UnregisterHandler<T> u)
unregister = u;
else if (unregister != null)
unregister(this);
Then, we just update our source to have an Unregister method. As a matter of fact, now that we have this weak handler goop, let’s encapsulate it inside a new Register method. This means that (unfortunately) we won’t be using the C# syntax for event registration, but compared with having users grok the weak handler mechanism, I personally find it much more palatable:
class Source { public event EventHandler<EventArgs> Tick; public void OnTick() { EventHandler<EventArgs> t = Tick; if (t != null) t(this, new EventArgs()); } public void Register(ISink sk) { Tick += new WeakEventHandler<EventArgs>(sk.HandleTick, Unregister).Handler; } private void Unregister(WeakEventHandler<EventArgs> eh) { Tick -= eh.Handler; } }
public void Register(ISink sk)
Tick += new WeakEventHandler<EventArgs>(sk.HandleTick, Unregister).Handler;
private void Unregister(WeakEventHandler<EventArgs> eh)
Tick -= eh.Handler;
Now the three lines of code change to:
Source src = /*...*/; MySink sk = new MySink(); src.Register(sk);
src.Register(sk);
Should I actually do this?
Just because you can use this doesn't mean you should. :) It seems like a reasonable strategy to make sure you don't shoot yourself in the foot too badly, but if you're relying on weak wrappers to do the unregistration and cleanup for you, it seems like all you're really doing is working around bugs. This is a bit like forgetting to call Dispose on an object and having a finalizer kick in just to make sure you don't leak. Further, you probably shouldn't use this if all you do is register for an event and drop the reference. In this case, the GC could kick in as early as the nanosecond after you drop the reference, meaning your registration was useless.
With that said, I think it's perfectly reasonable to use this to debug and detect bugs in your code. For example, if you fired an assert instead of unregistering the weak handler, you might be able to detect where in your program you forget to unregister sink objects. Just some thoughts. I'd love to hear any feedback on where you might find this useful.
Testing, Testing, 1, 2, 3...
This little snippet of test code demonstrates the whole thing. Here’s the full source in case anybody wants to play around with it without having to scour this post to pull together all the code.
class Program { static void Main() { Program p = new Program(); p.Execute(); } private Source src = new Source(); public void Execute() { for (int j = 0; j < 1000; j++) { for (int i = 0; i < 1000; i++) { src.Register(new MySink()); } src.OnTick(); GC.Collect(); } } }
class Program
static void Main()
Program p = new Program();
p.Execute();
private Source src = new Source();
public void Execute()
for (int j = 0; j < 1000; j++)
for (int i = 0; i < 1000; i++)
src.Register(new MySink());
src.OnTick();
GC.Collect();