Tuesday, October 20, 2009

A Weak Post

The WeakReference is one of those fun interview topics. Some people have never heard of it and when they learn about it will say, "That's a textbook question ... why would I ever use a Weak Reference?"

A weak reference references an object while still allowing that object to be reclaimed by garbage collection.

Why would you want to do that?

A common example you might see on the web is for a cache. For example, consider implementing functionality that relies on a service call for some information. You wouldn't mind holding onto it for awhile and avoiding the network round trip, but you also don't want it to fill up memory. Something like this might do the trick:

public class MyContainer 
{
    private WeakReference _myCachedObject; 

    public MyObject GetMyObject() 
    {
        if (_myCachedObject.IsAlive) 
        {
           return _myCachedObject.Target as MyObject;
        }
        else 
        {
            MyObject retVal = _FetchObjectFromService(); 
            _myCachedObject = new WeakReference(retVal, false);
            return retVal;
        }
    }
}

Of course, I've left out the implemenation of fetching this, but you get the point. While that is an interesting example, how about a more practical one? One of the most common causes of memory leaks in Silverlight/WPF applications is related to the registering and unregistering of events. To learn more about this memory leak (and also learn how to troubleshoot/debug/and find the source of the leak), read this article about finding memory leaks in Silverlight.

What makes these types of issues so inviting is the entire dependency property system. It is tempting, for example, to do something like I demonstrated in an earlier post about Silverlight Behaviors and Triggers.

In the post, I demonstrated a way to register story boards and then fire them using behaviors. I stored the collection of storyboards to trigger in a dictionary, like this:


public static class StoryboardTriggers
{
    private static readonly Dictionary<string, Storyboard> _storyboardCollection = new Dictionary<string, Storyboard>();
}

This creates a mapping between a key and the Storyboard instance. The problem is that this static class stays in scope through the duration of the Silverlight application. This means that the storyboards will never go out of scope! For a larger application, this could become a serious issue. When the control with the storyboard goes out of scope, the runtime should be allowed to release that memory. In this case, the reference in the dictionary will ensure that the storyboard object is never garbage collected (nor anything else in the object graph of the storyboard).

How do we fix this? This is where WeakReference comes to the rescue! Instead of keeping strong references to the Storyboard, we'll use WeakReferences instead. As long as the storyboard remains in scope, the WeakReference will contain a valid pointer to the class. If it goes out of scope, however, our weak reference won't keep it from being garbage collected and will allow it to gracefully "degrade" into the garbage compactor. The refactored code looks like this (focusing on the changes to the existing code as published in my previous post):


public static class StoryboardTriggers
{
    private static readonly Dictionary<string, WeakReference> _storyboardCollection = new Dictionary<string, WeakReference>();
}

public static void StoryboardKeyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    Storyboard storyboard = obj as Storyboard;
    if (storyboard != null)
    {
        if (args.NewValue != null)
        {
            string key = args.NewValue.ToString();
            if (!_storyboardCollection.ContainsKey(key))
            {
                _storyboardCollection.Add(key, new WeakReference(storyboard,false));
                storyboard.Completed += _StoryboardCompleted;
            }
        }               
    }
}

static void _StoryboardCompleted(object sender, System.EventArgs e)
{
   ((Storyboard)sender).Stop();
}

And the trigger itself simply needs to check to see if the storyboard is still in scope:


static void _SelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    string key = GetStoryboardSelectionChangedTrigger((DependencyObject) sender);
    if (_storyboardCollection.ContainsKey(key))
    {
        Storyboard storyboard = _storyboardCollection[key].Target as Storyboard;
        if (storyboard != null)  
        {
           storyboard.Begin();
        }
        else 
        {
           _storyboardCollection.Remove(key);
        }                  
    }
}

Now we keep our application clean and allow objects to degrade as needed.

Jeremy Likness

No comments:

Post a Comment