Friday 1 August 2014

.NET Event Memory Leaks

A common cause of memory leaks in .NET applications is caused by subscribing classes not correctly unsubscribing from events of another class it consumes prior to disposal.

The example below shows where memory leaks can cause a problem (copy and paste into LinqPad):
void Main()
{
 //subscriber subscribes to publisher event
 PublisherSubscriberExample(true);

 //subscriber does NOT subscribes to publisher event
 PublisherSubscriberExample(false);
}

void PublisherSubscriberExample(bool subscribeToEvents)
{
 String.Format("SubscribeToEvents: {0}", subscribeToEvents).Dump();
 Publisher pub = new Publisher();
 Subscriber sub = new Subscriber(pub, subscribeToEvents);
 WeakReference refPub = new WeakReference(pub);
 WeakReference refSub = new WeakReference(sub);
 //print publisher and subscriber object state pre dispose
 String.Format("Pub: {0}", refPub.IsAlive).Dump();
 String.Format("Sub: {0}", refSub.IsAlive).Dump();
 //dispose of the subscriber object
 sub = null;
 //force garbage collection for the subscriber object
 GC.Collect();
 //print publisher and subscriber object state post dispose
 String.Format("Pub: {0}", refPub.IsAlive).Dump();
 String.Format("Sub: {0}", refSub.IsAlive).Dump();
}

class Publisher
{
 public event EventHandler PublisherEvent;

 public Publisher() {}
}

class Subscriber
{
 Publisher _publisher;

 public Subscriber(Publisher publisher, bool subscribeToEvent)
 {
  _publisher = publisher;
 
  if (subscribeToEvent)
   _publisher.PublisherEvent += PublisherEvent;
 }

 void PublisherEvent(object sender, EventArgs e)
 {
 }
}
We have a Subscriber and Publisher class; the Subscriber takes a Publisher instance and subscribes to an event.  When the Subscriber instance goes out of scope (i.e. disposed of and requested for garbage collection), the Publisher instance still maintains a reference to the Subscriber as the Subscriber has NOT explicitly unsubscribed from its original subscription. Whilst the Publisher instance is still in scope so will the Subscriber instance.

To get round this issue it is therefore good practice to implement IDisposable on the Subscriber class so that the event can be unsubscribed from upon disposing of the object:
class Subscriber : IDisposable
{
 Publisher _publisher;

 public Subscriber (Publisher publisher, bool subscribeToEvent)
 {
  _publisher = publisher;
 
  if (subscribeToEvent)
   _publisher.PublisherEvent += PublisherEvent;
 }

 public void Dispose()
 {
  //free managed resources
  this.Dispose(true);
 }

 void Dispose(bool disposing)
 {
  if (disposing)
  {
   //free managed resources
   _publisher.PublisherEvent -= PublisherEvent;
  }
 }

 void PublisherEvent(object sender, EventArgs e)
 {
 }
}
You would then call Dispose on objects which expose this method instead of setting the reference to NULL.

No comments:

Post a Comment