FluidMoveBehavior Bug (& Memory Leak!)

Update (May-06-2016): I am not sure whether this bug is fixed in the latest version of WPF. However, I have rewritten the ToggleSwitch control from scratch which completely avoids this issue. You can check it here.

Back in August, two people, tmccowan and Bullimann, brought into my notice a bug associated with the FluidMoveBehavior in WPF. This issue can be found here. Since one of the controls (ToggleSwitch) in my WPFSpark project was using this behavior, the issue was preventing the garbage collection of the control. According to the post, the static TagDictionary in FluidMoveBehaviorBase is holding references of the associated objects and thus preventing them from being GCed.

Steve Greatrex posted a workaround subscribe to the UserControl.Unloaded event and in the handler call the Detach method of the FluidMoveBehavior.
I tried this workaround and did a memory profiling with ANTS Memory Profiler  (a really great tool!). To my dismay, this workaround had little effect. I thought the Detach method would remove the reference of the ToggleSwitch’s internal Grid but it didn’t. So the instances of ToggleSwitch kept piling in memory each time they were instantiated. (see images)

MemProfile1

MemProfile2
I debugged the code and dug in deeper into the FluidMoveBehaviorBase to see the contents of the TagDictionary object. In this dictionary, the Key is the object to which FluidMoveBehavior is added and the Value is an internal class called TagData which encapsulates the following properties: The object itself(Child), the object’s Parent, ParentRect, AppRect, TimeStamp and InitialTag.
So I thought of a different workaround using Reflection. In the UserControl.Unloaded event handler:
1. Get access to the TagDictionary field in the FluidMoveBehaviorBase.
2. Invoke the ContainsKey method to check if the object is added as a key in the dictionary.
3. If ContainsKey method returns true, then invoke the Remove method to remove the object from the dictionary.

private void OnUnloaded(object sender, RoutedEventArgs e)
{
    this.Unloaded -= OnUnloaded;
    DetachFluidMoveBehavior(rootGrid);
}

private void DetachFluidMoveBehavior(DependencyObject depObj)
{
    if (depObj != null)
    {
        foreach (var b in Interaction.GetBehaviors(depObj).OfType<FluidMoveBehavior>())
        {
            b.IsActive = false;
            b.Detach();
            // HACK: This hack is to remove the depObj from the TagDictionary collection using reflection as 
            // the TagDictionary collection is holding up the reference of depObj leading to its non-garbage collection of depObj (a.k.a Leak!)
            FieldInfo fieldInfo = typeof(FluidMoveBehaviorBase).GetField("TagDictionary", BindingFlags.Static | BindingFlags.NonPublic);
            if (fieldInfo == null)
                continue;

            // Get the TagDictionary object
            var tagDict = fieldInfo.GetValue(b);

            // Get the ContainsKey MethodInfo
            MethodInfo miContainsKey = fieldInfo.FieldType.GetMethod("ContainsKey");
            if (miContainsKey != null)
            {
                // Does the TagDictionary contain depObj as a key?
                bool containsKey = (bool)miContainsKey.Invoke(tagDict, new object[] { depObj });
                if (containsKey)
                {
                    // Get the Remove Method Info
                    MethodInfo miRemove = fieldInfo.FieldType.GetMethod("Remove");
                    if (miRemove != null)
                    {
                        // Remove the depObj from the TagDictionary
                        miRemove.Invoke(tagDict, new object[] { depObj });
                    }
                }
            }
        }
    }
}

Though this is a crude solution, it does its job.

This workaround has been incorporated in WPFSpark v1.2 which I will be releasing soon.

I hope Microsoft fixes this issue in the next release. 🙂

Advertisements

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