Simple Weak Event Listener for Silverlight

The problem

When using normal CLR events, registering an event handler creates a strong reference from the event source to the listening object.

clip_image002

If the event-source (our ViewModel) has a longer lifetime than the listener and the listener gets released from the hosting app (in our case the visual tree) then it can’t be garbage collected, because the long living event-source instance has still a reference to the listener through the delegate.

clip_image004

Unfortunately, visual tree elements don’t provide an UnLoaded event. So I’m not able to unregister the event handler from the event-source. For example when using the event-source for multiple pages as listeners these are alive until the event source gets released. This is a hidden memory leak in our Silverlight application.

clip_image006

It is not something new and there are several solutions out there. Most of the solutions using Reflection or dynamic code generation via Reflection.Emit which is not working in Silverlight because of limited reflection permission in Silverlight or it suffer from performance issues.

But how does the Silverlight Toolkit Team deal with this problem? For example, listening for INotifyPropertyChanged or INotifyCollectionChanged in any control is a common task when implementing a custom control.

The solution

Indeed, the Silverlight Toolkit source contains a very simple solution for this problem. The class is called WeakEventListener but its visibility is internal. I just made a copy of the class (thanks to the Microsoft Public License) to use it in our project.

When using this class you should pay attention to two implementation detail otherwise the application still has memory leaks (it think this is the reason why Microsoft did not make the class public). I try to explain it in the rest of this post.

How it works

The WeakEventListener operates as an instance between the event-source and the listener and contains the event handling part.

clip_image008

The WeakEventListener associates the listener instance via a WeakReference. It’s very import that the listener just creates the WeakEventListener instance but never holds a reference to it. This together ensures that the listener can be garbage collected when the App instance releases the listener.  UPDATE 30.03.2010: Based on the feedback from Eric Garnier (see comments below) I did a small change in the WeakEventListener class. Internally the WeakEventListener has a reference back to the EventSource. This reference is needed to unregister the event handler as soon the listener gets released from the GC. This reference is now implemented as a WeakReference. Now, it doesn’t matter if you have the WeakEventListener referenced from the listener or not. The attached source code includes the changes.

 

clip_image010

How to use in code

// register for event (weak pattern)
EventSource longLivingInstance = this.DataContext as EventSource;

// create WeakEventListener. Pass Listener and EventSource instance to it.
var weakListener = new WeakEventListener<Listener, EventSource, 
PropertyChangedEventArgs>(this, longLivingInstance); // register handler longLivingInstance.PropertyChanged += weakListener.OnEvent; // define event handler to handle the event. weakListener.OnEventAction = (instance, source, e) => { // event handling code instance.longLivingInstance_PropertyChanged(instance, e); }; // define action to detach the handler weakListener.OnDetachAction = (listener, source) => { // unregister handler source.PropertyChanged -= listener.OnEvent; }; // ensure there is no reference from the listener to the weakListener weakListener = null;

Step-by-Step explained

First create an instance of the WeakEventListener with the relevant type arguments from the Listener, EventSource and EventArgs.

// create WeakEventListener. Pass Listener and EventSource instance to it.
var weakListener = new WeakEventListener<Listener, EventSource, 
PropertyChangedEventArgs>(this, longLivingInstance);
 

As next, register the OnEvent method from the weakListener as event-handler.

// register handler
longLivingInstance.PropertyChanged += weakListener.OnEvent;
 

Then defining the event handler method an assign it to the OnEventAction delegate.

// define event handler code.
weakListener.OnEventAction = (instance, source, e) =>
{
    // event handling code
    instance.longLivingInstance_PropertyChanged(instance, e);
};

The event handler method must be static. Why this is important? The OnEventAction is a delegate to a method and this method defines the action when the event is raising. It is important that the delegate to this method has no Target set and this is the case when the target method is static.

clip_image002[5]

How it works with lambda statement? When writing a lambda statement, the compiler generates an anonymous method which is static:

[CompilerGenerated]
private static void <Listener_Loaded>b__0(Listener instance, object source, PropertyChangedEventArgs e)
{
    instance.longLivingInstance_PropertyChanged(instance, e);
}
 

But when one accesses any instance member inside the OnEventAction method then the compiler generates an instance method instead of a static.

[CompilerGenerated]
private void <Listener_Loaded>b__0(Listener instance, object source, PropertyChangedEventArgs e)
{
    this.longLivingInstance_PropertyChanged(instance, e);
} 
 

Because of this small difference the delegate has now a reference back to the listener and the memory leak still remains (only with a little more code…)

clip_image002[7]

The debugger shows it clearly.

clip_image004[6]

This makes the detail whether the WeakEventListener works correctly or not. Therefore, I extended the original WeakEventListener code to prevent this mistake. The OnEventAction property checks in its setter whether the assigned delegate pointing to a static method or not.

 
public Action<TInstance, object, TEventArgs> OnEventAction
{
    get { return _onEventAction; }
    set
    {
        if (value != null && !value.Method.IsStatic)
            throw new ArgumentException("OnEventAction method must be static otherwise the " +
                                        "event WeakEventListner class does not prevent memory leaks.");

        _onEventAction = value;
    }
}
 
Lastly, the weakListener needs an action to unregister the event handler.
 
// define action to detach the handler
weakListener.OnDetachAction = (listener, source) =>
{
    // unregister handler
    source.PropertyChanged -= listener.OnEvent;
};
 

The OnDetachAction is called when one calls the Detach method on the weakListener or when the listener isn’t alive anymore and the event-source is raising events. Here I did another small change in the source code: the OnDetachAction provides the event-source instance as the second parameter. With this the WeakEventListener instance can guarantee to unregister the source-event even though all others had released the EventSource. In other words the WeakEventListener instance can unregisters itself from the EventSource. To get a safety implementation of this rule the OnDetachAction property does the same static-method check as the OnEventAction property.

Summary

The Silverlight Toolkit contains an excellent implementation of a WeakEventListener. It doesn’t use reflection or dynamic assembly generation, so you don’t bother with performance or security problems. The only problem I see is that it can be used incorrectly. Therefore, I slightly modified the WeakEventListener with some runtime checks to prevent these mistakes.

In a next post I’m going to document an additional wrapper-pattern I’ve build around the WeakEventListener. The wrapper is designed as an easy to use weak event listener for a specify event source such as the common PropertyChanged and CollectionChanged events from the INotifyPropertyChanged and INotifyCollectionChanged interfaces.

TestViewModel longLivingInstance = new TestViewModel();
NotifyPropertyChangedListener listener = new NotifyPropertyChangedListener(longLivingInstance);
listener.PropertyChanged += new PropertyChangedEventHandler(listener_PropertyChanged);

 

And I want to explain why for us the weak event pattern is such much important when writing custom controls or making async service calls.

Source Code

Here you can find the full source code.

image

Modified WeakEventListener class (original is in Silverlight Toolkit):

//-----------------------------------------------------------------------
//  This source is a slightly modified version from the WeakEventListner
//  in the Silverlight Toolkit Source (www.codeplex.com/Silverlight)
//---------------------------Original Source-----------------------------
// <copyright company="Microsoft">
//      (c) Copyright Microsoft Corporation.
//      This source is subject to the Microsoft Public License (Ms-PL).
//      Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
//      All other rights reserved.
// </copyright>
//-----------------------------------------------------------------------

using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;

namespace System.Windows.Controls
{
    /// <summary>
    /// Implements a weak event listener that allows the owner to be garbage
    /// collected if its only remaining link is an event handler.
    /// </summary>
    /// <typeparam name="TInstance">Type of rootInstance listening for the event.</typeparam>
    /// <typeparam name="TSource">Type of source for the event.</typeparam>
    /// <typeparam name="TEventArgs">Type of event arguments for the event.</typeparam>
    internal class WeakEventListener<TInstance, TSource, TEventArgs> where TInstance : class
    {

        #region Fields

        /// <summary>
        /// WeakReference to the rootInstance listening for the event.
        /// </summary>
        private WeakReference _weakInstance;

        /// <summary>
        /// To hold a reference to source object. With this instance the WeakEventListener 
        /// can guarantee that the handler get unregistered when listener is released.
        /// </summary>
        private WeakReference _weakSource;

        /// <summary>
        /// Delegate to the method to call when the event fires.
        /// </summary>
        private Action<TInstance, object, TEventArgs> _onEventAction;

        /// <summary>
        /// Delegate to the method to call when detaching from the event.
        /// </summary>
        private Action<WeakEventListener<TInstance, TSource, TEventArgs>, TSource> _onDetachAction;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instances of the WeakEventListener class.
        /// </summary>
        /// <param name="rootInstance">Instance subscribing to the event.</param>
        public WeakEventListener(TInstance instance, TSource source)
        {
            if (null == instance)
            {
                throw new ArgumentNullException("instance");
            }

            if (source == null)
                throw new ArgumentNullException("source");

            _weakInstance = new WeakReference(instance);
            _weakSource = new WeakReference(source);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the method to call when the event fires.
        /// </summary>
        public Action<TInstance, object, TEventArgs> OnEventAction
        {
            get { return _onEventAction; }
            set
            {
                // CHANGED: NEVER REMOVE THIS CHECK. IT CAN CAUSE A MEMORY LEAK.
                if (value != null && !value.Method.IsStatic)
                    throw new ArgumentException("OnEventAction method must be static "+
                              "otherwise the event WeakEventListner class does not prevent memory leaks.");

                _onEventAction = value;
            }
        }

        /// <summary>
        /// Gets or sets the method to call when detaching from the event.
        /// </summary>
        internal Action<WeakEventListener<TInstance, TSource, TEventArgs>, TSource> OnDetachAction
        {
            get { return _onDetachAction; }
            set
            {

                // CHANGED: NEVER REMOVE THIS CHECK. IT CAN CAUSE A MEMORY LEAK.
                if (value != null && !value.Method.IsStatic)
                    throw new ArgumentException("OnDetachAction method must be static otherwise " +
                               "the event WeakEventListner cannot guarantee to unregister the handler.");

                _onDetachAction = value;
            }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Handler for the subscribed event calls OnEventAction to handle it.
        /// </summary>
        /// <param name="source">Event source.</param>
        /// <param name="eventArgs">Event arguments.</param>
        public void OnEvent(object source, TEventArgs eventArgs)
        {
            TInstance target = (TInstance)_weakInstance.Target;
            if (null != target)
            {
                // Call registered action
                if (null != OnEventAction)
                {
                    OnEventAction(target, source, eventArgs);
                }
            }
            else
            {
                // Detach from event
                Detach();
            }
        }

        /// <summary>
        /// Detaches from the subscribed event.
        /// </summary>
        public void Detach()
        {
            // CHANGED: 30.03.2010


TSource source = (TSource)_weakSource.Target;
            if (null != OnDetachAction && source != null)
            {
                // CHANGED: Passing the source instance also, because of static event handlers
                OnDetachAction(this, source);
                OnDetachAction = null;
            }
        }

        #endregion
    }
}
About these ads
Tagged ,

13 thoughts on “Simple Weak Event Listener for Silverlight

  1. [...] a previous post I explained in detail why there can be a memory leak and how we prevent it in our SL controls. The [...]

  2. Eric Garnier says:

    Why did you said:
    It’s very import that the listener just creates the WeakEventListener instance but never holds a reference to it.

    After all, in the diagram, this does not prevent the listener from been garbaged.

    Eric

    • beatkiener says:

      Hi Eric,
      You are absolutely right. When I’m looking to the diagram again my sentence does not make a sense. I was wondering why I highlighted this fact so much. But when I’m looking to the code the point gets again clear to me. The WeakEventListener instance holds a back reference to the EventSource instance, because the listener guarantees to deregister the event handler when the target instance was collected. Unfortunately I forgot to add this reference in the diagram. Maybe I could use a WeakReference back to the EventSource to deregister the handler. Let me check that. Anyway, I will update the post.

      Thanks for your feedback.

      Best regards,
      Best

  3. Christian Weyer says:

    Very nice article, thanks for it.
    Do you happen to have an example of calling a service async via the service agent pattern?

    Thanks and cheers.

    • beatkiener says:

      Hi Christian,

      Thank you.
      No, unfortunately not. Recently I’ve started with creating my own ChannelFactory.CreateChannel method which generates the Weak-Pattern as a hidden part with System.Reflection.Emit. But for now we use just one proxy instance for one view, so the memory leak problem isn’t there for us.

      Maybe I will find time to finish this task… :)

      Cheers

  4. Christian Weyer says:

    Well, yes and no :)
    If you use a local service proxy instance per service agent and pass lambdas into the service agent methods, then you have the exact same problem…

  5. [...] Vous pouvez lire sont billet (en anglais) en complément de celui-ci (il expose plus en détail le fonctionnement de la classe, et pour illustrer mon propos je lui ai emprunté les schémas – rendre à César ce qui est sien est important) : http://blog.thekieners.com/2010/02/11/simple-weak-event-listener-for-silverlight/ [...]

  6. Wonderful I highly recommend this site!

  7. Immortal says:

    How can we adapt the code for Windows 8 Metro Style apps? There’s a problem with the IsStatic check – apparently the Action.Method property is no longer present.

  8. Shimmy says:

    It’s pretty limited having the method only static.
    I’d be happier if the implementation would be IWeakEventHandler and WeakEventManager WPF-like style.

    Any news will be welcommed thanks for posting anyway.

  9. Shimmy says:

    Is there a way to invoke an instance method in my ViewModel on a fired event?

  10. John says:

    I downloaded this code, and although it runs and fires events, when I press the REmove Instance and step it through, it does not print out the message “Listener.Finalize” from the listener as the article suggests. I will look at it again to see if I missed anything, but don’t think I have.

  11. [...] Vous pouvez lire sont billet (en anglais) en complément de celui-ci (il expose plus en détail le fonctionnement de la classe, et pour illustrer mon propos je lui ai emprunté les schémas – rendre à César ce qui est sien est important) : http://blog.thekieners.com/2010/02/11/simple-weak-event-listener-for-silverlight/ [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: