Thursday, 9 June 2011

Strong references - Reactive Extensions

I thought I would have a quick look to determine wether Rx would create a strong reference under the hood or not when one object subscribed to another object's events.

In the example below the Consumer object subscribes to the SampleData objects's SampleDataChanged event. This type of strong reference causes memory leaks in applications because the garbage collector will not clean up the event consumer even though the consumer may be out of scope. The result of my testing indicates that unfortunately there is still a strong reference because once the Consumer object goes out of scope and once Garbage collection has been called, calling the event on SampleData still causes the event to be raised in the Consumer meaning that the consumer has not been garbage collected.

Can I use the System.WeakReference object to resolve this issue? I will follow up on this shortly...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace StrongReferenceTestConsole
{
    class Program
    {

        static void Main(string[] args)
        {
            SampleData sampleData = new SampleData();
            for (int i = 0; i < 3; i++)
            {
                // Create a consumer which registers a method with the sample data objects SampleDataChanged event
                using (Consumer consumer = new Consumer(sampleData))
                {
                    consumer.Name = "Consumer " + i.ToString();

                    //Raise the sample data object's data changed event
                    sampleData.RaiseSampleDataChanged("Work on data - iteration : " + i.ToString());
                }
            }

            sampleData.RaiseSampleDataChanged("Sample Data change - Consumer outside scope");

            // Force garbage collection
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            Console.ReadLine();
        }

    }

    public class Consumer : IDisposable
    {
        IDisposable _subscription;

        public Consumer(SampleData sampleData)
        {

            // The usual method for event subscription
            //sampleData.SampleDataChanged += new SampleData.SampleDataChangedEventHandler(Consumer_SampleDataChanged);

            //USE RX to subscribe to events
            _subscription = Observable.FromEvent<SampleDataChangedEventArgs>(sampleData, "SampleDataChanged").Subscribe(
                args =>
                Consumer_SampleDataChanged(sampleData, args.EventArgs)
                );
        }

        public string Name { get; set; }

        void Consumer_SampleDataChanged(object sender, SampleDataChangedEventArgs e)
        {
            Console.WriteLine("Sample Data Changed : {0} - Notified Consumer : {1}", e.Message, this.Name);
        }

        ~Consumer()
        {
            Console.WriteLine("Consumer finalizer");
        }

        #region IDisposable Members

        public void Dispose()
        {
            //UNCOMMENT TO REMOVE MEMORY LEAK - IDisposable is used in Rx to release strong reference.
            //if (_subscription != null) _subscription.Dispose();

            Console.WriteLine("Consumer Dispose");
        }

        #endregion
    }

    /// <summary>
    /// SampleData object which exposes event SampleDataChanged
    /// </summary>
    public class SampleData
    {
        public event EventHandler<SampleDataChangedEventArgs> SampleDataChanged;
        public void RaiseSampleDataChanged(string message)
        {
            if (SampleDataChanged != null)
            {
                Console.WriteLine("SampleDataChanged event has " + SampleDataChanged.GetInvocationList().Count() + " delegates wired up.");

                SampleDataChangedEventArgs e = new SampleDataChangedEventArgs(message);
                SampleDataChanged(this, e);
            }
        }
    }

    public class SampleDataChangedEventArgs : EventArgs
    {
        public SampleDataChangedEventArgs(string message)
        {
            this.Message = message;
        }
        public string Message { get; set; }
    }

}

Console output with memory leak:


Console output without memory leak: