Friday, 20 April 2012

Weak Event Handlers

Why would you need to use a Weak Event handler?  Take a look at this example of normal events and handlers:

image

Notice how the Console window is empty.  The Subscriber finalizer ~Subscriber() which writes to the console window has not been called meaning our subscriber is still in memory. This is due to the Publisher event holding a strong reference to the Subscribers event handler (see the watch window).

image

Why not just implement IDispose and unsubscribe from the event?

image

This is the ideal scenario but you are relying upon our Publisher being considerate enough to call dispose on the Subscriber.  This means we can remove the event handler, notice how the publishers event is now null, which means the GarbageCollector can collect our subscriber. 

But what happens if the Publisher forgets to “dispose” of the subscriber?  We still have a memory leak.

Weak Reference Events to the rescue!

image

We can see here in the watch window that the publishers event still has a reference to the subscribers event handler.  But the subscriber has been disposed…. cool!

This is all possible that to the use of a WeakReference.

Special thanks to Distin Campbell, Joe Duffy, Xavier Musy and Gregor R. Peisker.

using System;
using System.Reflection;

public delegate void UnregisterCallback<E>(EventHandler<E> eventHandler)
where E : EventArgs;

public interface IWeakEventHandler<E>
where E : EventArgs
{
EventHandler<E> Handler { get; }
}

public class WeakEventHandler<T, E> : IWeakEventHandler<E>
where T : class
where E : EventArgs
{
private delegate void OpenEventHandler(T @this, object sender, E e);

private WeakReference _targetRef;
private OpenEventHandler _openHandler;
private EventHandler<E> _handler;
private UnregisterCallback<E> _unregister;

public WeakEventHandler(EventHandler<E> eventHandler, UnregisterCallback<E> unregister)
{
_targetRef = new WeakReference(eventHandler.Target);
_openHandler = (OpenEventHandler) Delegate.CreateDelegate(
typeof(OpenEventHandler), null, eventHandler.Method);
_handler = Invoke;
_unregister = unregister;
}

public void Invoke(object sender, E e)
{
T target = (T) _targetRef.Target;

if (target != null)
_openHandler.Invoke(target, sender, e);
else if (_unregister != null)
{
_unregister(_handler);
_unregister = null;
}
}

public EventHandler<E> Handler
{
get { return _handler; }
}

public static implicit operator EventHandler<E>(WeakEventHandler<T, E> weakEventHandler)
{
return weakEventHandler._handler;
}
}

public static class EventHandlerUtils
{
public static EventHandler<E> MakeWeak<E>(this EventHandler<E> eventHandler, UnregisterCallback<E> unregister)
where E : EventArgs
{
if (eventHandler == null)
throw new ArgumentNullException("eventHandler");
if (eventHandler.Method.IsStatic || eventHandler.Target == null)
throw new ArgumentException("Only instance methods are supported.", "eventHandler");

Type weakEventHandlerType = typeof(WeakEventHandler<,>).MakeGenericType(
eventHandler.Method.DeclaringType, typeof(E));

ConstructorInfo weakEventHandlerConstructor = weakEventHandlerType.GetConstructor(
new Type[] { typeof(EventHandler<E>), typeof(UnregisterCallback<E>) });

IWeakEventHandler<E> weakEventHandler = (IWeakEventHandler<E>)weakEventHandlerConstructor.Invoke(
new object[] { eventHandler, unregister });

return weakEventHandler.Handler;
}
}

Performance


Who wants to see a shoot out between strong event handlers and our weak event handler solution?
Ok, so first up here’s the code for our benchmarks:

using System;
using System.Diagnostics;

class Performance
{
const int iterations = 1000000;

public static void Main()
{
Console.WriteLine("Running tests with {0} iterations", iterations);

RunTest("StrongEvent", new StrongEvent.Publisher());
RunTest("StrongEventDisposable", new StrongEventDisposable.Publisher());
RunTest("WeakEvent", new WeakEvent.Publisher());
}

public static void RunTest(string name, IPublisher publisher)
{
GC.Collect();
GC.WaitForPendingFinalizers();

var stopwatch = Stopwatch.StartNew();

for (int i = 0; i < iterations; i++)
{
publisher.CreateSubscriber();
publisher.DestroySubscriber();
}

stopwatch.Stop();
Console.WriteLine("{0} took {1} ms", name, stopwatch.ElapsedMilliseconds);
}
}

Results


image


Ouch!  You win some, you lose some Smile

The results are very interesting, in conclusion it really does pay to be a good .NET citizen, removing event handlers and disposing of an IDisposable wins here.

Microsoft have come up with the Weak Event Pattern for WPF: http://msdn.microsoft.com/en-us/library/aa970850.aspx
I wonder if Microsoft will come up with a better and more generic solution for weak event handlers in .NET 4.5.


Source


http://stevenhollidge.com/blog-source-code/WeakEventReference.zip

Note:  The finalizers have been commented out in the code so as not to affect the performance tests.

No comments:

Post a Comment