Memory leak with WPF animations

It seems there is bug in the WPF animation architecture that can cause memory not to be released and causing performance and stability problems. The document describes when the problem occurs and why.

During the development of a WPF application, we noticed that a huge amount of memory was used after a while. After profiling with the excellent .NET Memory Profiler v3.1 we found out that a lot of objects were not collected by the garbage collector.

After some heavy investigation I found out that a FrameworkTemplate was holding references to a lot of my textboxes that should have been collected already. These textboxes held references to other objects, so the result was that a lot of memory was wasted. When the event triggers with actions were removed from the textbox’s control template, then the memory was released properly.

When does the problem occur?
The problem occurs in the following situation:

  1. The control has a control template defined.
  2. The control template uses triggers with enter/leave actions.
  3. The control has the ‘Collapsed’ visibility.

Why does the problem occur?
Triggers are active during the initialization of a control. If a property triggers an enter/leave action, then the action is started. The action is started via the internal “StyleHelper.InvokeActions” method. This method checks if the visual tree of the object is already constructed and if it is, then the actions are invoked immediately.

If the visual tree hasn’t been created yet, then the action is queued by the “StyleHelper.DeferActions”, so it can be processed later. To my surprise, the deferred actions are stored with the template and not the control itself (IMHO: This is a fundamental design flaw). If the visual tree is created, then the deferred actions will be invoked.

The visual tree is created during the public “FrameworkElement.ApplyTemplate” method. The framework only calls this method when measuring the control. So if the control is never measured, then the visual tree is not created and the deferred actions will remain queued forever.

The MSDN documentation for “FrameworkElement.MeasureOverride” clearly states:

Important Note:
Elements should call Measure on each child during this process, otherwise the child elements will not be correctly sized or arranged.

You might expect that all elements call “FrameworkElement.Measure” on their children, so during this process the “FrameworkElement.ApplyTemplate” is called. However, WPF doesn’t respect this rule, because “UIElement.Measure” doesn’t start measuring when the element has a collapsed visibility.

Collapsed UI elements are never measured and if they never will get the Visible or Hidden visibility, then the object is never measured and the template isn’t applied. This seems very logical, but it also causes the deferred actions never to be removed from the template.

Because the template keeps the reference to the control, the control is never garbage collected.  WPF controls know their parents and children. They also reference other objects via databinding. The result is that a lot of objects are referenced by the control (direct or indirect) and are not garbage collected.

Sample application
I have created a sample application that illustrates the problem. When the application is started, then you can see the deferred actions for the control template of the MySampleControl.

When you press the “Create Collapsed Expander”, the application creates a window that contains a collapsed expander that contains a MySampleControl. Because the MySampleControl is collapsed it isn’t constructed and you can see the control is added to the deferred actions list (after refreshing the list).

When you expand the expander on the window and refresh the deferred actions, then you can see that the control is removed from the list. If you close the window, without expanding the expander, then the control will stay on the list forever. Because the item is still on the list it is never collected.

You can force a garbage collection and in the debug window (when running in Visual Studio) you can see which objects are finalized. Windows with expanders that have never been opened will never be finalized.

Conclusion
I guess Microsoft made a design error by storing the deferred actions with the template. They should have been stored in the control itself. The deferred actions are only used when applying the template, which is an instance method of the control. So it would have had full access. Now a dictionary is used that connects objects that shouldn’t be connected.

Response from Microsoft
I have received a response from Microsoft and they confirm it is a bug. It is on the list to be fixed, but there is no releasedate for .NET 3.5 SP2 or .NET 4. So I guess we’ll have to wait.

Microsoft .NET Framework 4
Beta 1 of the Microsoft .NET Framework 4 has been released and it now uses a ConditionalWeakTable instead of a HybridDictionary. The keys are kept as weak references and when the key is collected by the GC, then the associated value is also released. This also solves this issue in our situation, because the dictionary doesn’t keep the objects alive anymore.

Sample application
Download the sample application.

11 thoughts on “Memory leak with WPF animations

  1. Hi,

    Fantastic post, this exactly describes what I’ve seen in my profiler, but I had no idea what was causing the problem. Do you have any suggestions for ways of clearing out those DeferredActions? I’m going to experiment with changing all of our visibility properties from Collapsed to Hidden, but it would be ideal if we could simply flush out the DeferredActions for our dead visuals.

    Any help would be greatly appreciated. Did Microsoft give a timeline for the fix?

    Dave

  2. There’s no way to clear the DeferredActions, without side effects. It also requires a lot of reflection, so I don’t bother to try and fix it myself. For now, we have disabled the affected animations.

    Please do report this issue to Microsoft. They haven’t had a lot of reports of this issue and that’s the reason why it wasn’t high on the fix list. I guess it isn’t reported often, because it’s so hard to track it down.

    MS will get back to me for a timeline for a fix. I don’t want a hotfix, because end-users probably don’t bother to install it and do report issues about memory load. I hope it will be incorporated in .NET 3.5 SP2 and .NET 4.0.

  3. I guess that changing the visibility to Hidden would solve the problem, because it will trigger the Measure method. But visually, hidden is something different from collapsed.

    You can also try to set a scaling layout transform with scale factors set to 0 to make sure it doesn’t take any space. I don’t know if Measure is called for these items (I guess it would). The disadvantage is that the entire visual tree is created, so it takes up a bit more resources. The advantage is that you have chances that memory is released at the end. :-)

  4. So in the end, the best approach was to modify the conditions of the EnterActions so that they wouldn’t launch unless the visual was actually being shown. This eliminated the need to do anything fancy. Also, we’ve pushed the issue to Microsoft and used your case number – they said that they’d try to get an ETA for us since this is already an open case.

    Thanks again for this info, you saved our bacon!

  5. At last, the reason for the leak! I’ve just spent the past 3 days slowing tracking down the leak in our code using the ANTS memory profiler and can’t believe it’s down to the animation is own overridden TextBox class. Really disappointed that there isn’t a fix from Microsoft yet, will be contacting them next.

    Many thanks for your explanation on the cause.

  6. Following on from my last post, in the interim, what’s the easiest way to determine whether your control has actually been drawn, and hence, could my create a dependency property on that within my control class that I could use as the extra condition on the EnterActions?

  7. Deferred actions are started by WPF during the Measure operation, so you can be sure that no leak will occur if your ‘MeasureOverride’ method has been called. You could set your dependency property the first time the Measure operation is called.

    Make sure you remove this behaviour once the bug is fixed. I got a reply from MS that it has been fixed in .NET 4.0 that will ship with the next Visual Studio release. Unfortunately, it will be released the second half of 2009. A beta will be provided earlier…

  8. Thank you so much for this.

    I have been investigating the exact similar issue for days, finally, concluded this was not a bug in our own implementation.

  9. Pingback: rubenhak.com » Identifying and Eliminating C#/WPF Memory Leaks

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>