Weak Event

2012/07/20

Weak Event

there is no doubted that event handler is the number one reason of memory leak under .NET framework.

recently I was part of a team which was tracing a memory leak out of a dump file, as expected the main issue was happens to be the prime suspect (event handler).

Memory leak, event, event handler, .net, proplem, Weak event

you can find plenty of resources on how and why careless usage of event handler can cause a memory leak (just Google it).

in this post I will present a general solution for this problem (which can be use when applicable).

the solution

the solution is to use some kind of weak reference abstraction over the event.
the weak event abstraction will allow the GC to collect the event handler’s instance even while it is subscribe to a rooted event (as long as the handler has no other rooted reference is will be collect).

you can download a sample solution from hear.

the solution is having 2 main parts,
WeakEvent and WeakEvent<T> which hold a similar implementation for EventHandler and EventHandler<T>.

I will shortly present the WeakEvent<T> but before that I want to present a usage of WeakEvent<T>.

suppose that you are having the following class which is having a single event called Notify:

Code Snippet
  1. public class Pub
  2. {
  3.     public event EventHandler<EventArgs> Notify;
  4. }

in order of adding a correlate weak event to this class you should apply 3 simple steps:

Code Snippet
  1. private event EventHandler<EventArgs> Notify;
  2.  
  3. private WeakEvent<EventArgs> _weakNotify;
  4. public event EventHandler<EventArgs> WaekNotify
  5. {
  6.     add
  7.     {
  8.         _weakNotify.WeekEvent += value;
  9.     }
  10.     remove
  11.     {
  12.         _weakNotify.WeekEvent -= value;
  13.     }
  14. }
  15.  
  16. public Pub()
  17. {
  18.     _weakNotify = new WeakEvent<EventArgs>(h => Notify += h, h => Notify -= h);
  19. }

step 1:

line 3, adding a private field of WaekEvent or WaekEvent<T>.

Step 2:

lines 4-14, add an event abstraction which will route event’s subscription to the weak event;

Step 3:

at the class constructor, assign the weak event with 2 delegate that register an unregister from the original event.

Behind the cine
Code Snippet
  1. public class WeakEvent<T> : IDisposable
  2.     where T:EventArgs
  3. {
  4.     private readonly object _sync = new object();
  5.     private readonly HashSet<EquatableWeakReference> _subscribers = new HashSet<EquatableWeakReference>();
  6.     private Action<EventHandler<T>> _unregister;
  7.  
  8.     public WeakEvent(Action<EventHandler<T>> register, Action<EventHandler<T>> unregister)
  9.     {
  10.         register(Handler);
  11.         _unregister = unregister;
  12.     }
  13.  
  14.     public event EventHandler<T> WeekEvent
  15.     {
  16.         add
  17.         {
  18.             var w = new EquatableWeakReference(value);
  19.             lock (_sync)
  20.             {
  21.                 _subscribers.RemoveWhere(t => !t.IsAlive);
  22.                 _subscribers.Add(w);
  23.             }
  24.         }
  25.         remove
  26.         {
  27.             lock (_sync)
  28.             {
  29.                 _subscribers.RemoveWhere(t => !t.IsAlive);
  30.                 _subscribers.Remove(new EquatableWeakReference(value));
  31.             }
  32.         }
  33.     }
  34.  
  35.     private void Handler(object sender, T e)
  36.     {
  37.         EventHandler<T>[] handlers;
  38.         lock (_sync)
  39.         {
  40.             handlers = (from item in _subscribers
  41.                         let h = item.Target as EventHandler<T>
  42.                         where h != null
  43.                         select h).ToArray();
  44.         }
  45.         foreach (var h in handlers)
  46.         {
  47.             h(sender, e);
  48.         }
  49.     }
  50.  
  51.     public void Dispose()
  52.     {
  53.         GC.SuppressFinalize(this);
  54.         Dispose(true);
  55.     }
  56.  
  57.     protected void Dispose(bool disposed)
  58.     {
  59.         _unregister(Handler);
  60.         _unregister = null;
  61.         _subscribers.Clear();
  62.     }
  63.  
  64.     ~WeakEvent()
  65.     {
  66.         Dispose(false);
  67.     }
  68. }

the root of this implementation is the HashSet<EquatableWeakReference> at line 5, which is used to keep a weak reference of the original subscribers.

EquatableWeakReference is a class which derive from WeakReference and handle equality and get hash code of the internal target (the class was written by Eli Arbel and it is part of the sample download).

because event is not a .NET first class citizen the constructor is getting 2 delegates (line 8) which will be use to register and unregister from the original event.

the heart of the implementation is at the event routing (lines 14-33), which maintain the weak reference data structure.

and a mediator event handler (line 35-49) which hook the original event and route the data into the weak subscribers.

finally a Dispose pattern is been used as a good .NET citizen.

Summary

when ever you are having an event handler which its lifetime doesn’t need to be dictate by the event source, it is better to have a week event relationship in order to avoid a memory leaks.

Credits:

during the process of analyzing the memory leak and having a proper solution I was working with a few other developers and consultants, Oren, Eli and Roman, all was having an important contribution toward this solution.

Shout it kick it on DotNetKicks.com

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>