It seems there is bug in the WPF resource 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 DropShadowEffect was holding references to objects that should have been garbage collected already.
When does the problem occur?
The problem occurs in the following situation:
- A style is defined in the application’s ResourceDictionary.
- The style uses a control template that uses media effects (i.e. DropShadowEffect).
- The media effect should be referenced using a StaticResource.
Why does the problem occur?
When a media effect is attached to a UIElement, then the
UIElement.VisualEffectInternal property setter is invoked by the WPF framework. This method registers to the
Effect.Changed event when the media effect is not frozen. This is correct, because a non-frozen effect might change its properties and the UI element should invalidate. A frozen effect cannot be changed, so there is no need to register to the
This behavior is correct and also documented in the MSDN library in the chapter Optimizing Performance: Object Behavior:
Changed Handlers on Unfrozen Freezables may Keep Objects Alive
The delegate that an object passes to a Freezable object’s Changed event is effectively a reference to that object. Therefore, Changed event handlers can keep objects alive longer than expected. When performing clean up of an object that has registered to listen to a Freezable object’s Changed event, it is essential to remove that delegate before releasing the object.
WPF also hooks up Changed events internally. For example, all dependency properties which take Freezable as a value will listen to Changed events automatically.
Unfortunately, there seems to be no code in the WPF framework that removes the delegate again. The only way to unsubscribe is to set the Effect to NULL. Using media effects that are not frozen seems to be dangerous.
As long as the media effect is frozen, then the framework doesn’t subscribe to the Changed event and everything is fine. But that’s the problem… When using a merged resource dictionary at the application level, then the resources inside of it are not always frozen (see the example application). If the resource is marked explicitly to be frozen, then it will be frozen and everything works fine.
What kind of puzzled me, is the fact that when the effect and style are defined directly at the “Application.Resources” level (without a resource dictionary), then the effect is frozen by default. If they are defined in a resource dictionary that is merged inside the “Application.Resources” then the effect is not frozen! You can force the effect to be frozen by specifying
PresentationOptions:Freeze=True, but this is not common behavior. So, putting resources into resource dictionaries has side effects that are not foreseen and are not (clearly) documented in the WPF documentation.
Because the media effect is shared and the delegates are not removed when the UI element is not needed anymore it keeps the UI elements alive. WPF holds a strong hierarchy in objects, so a lot of other objects stay in memory too, resulting in a lot of wasted memory that can never be regained anymore.
The actual problem is that effects that are defined in a resource dictionary that is loaded from the Application object are not frozen in some cases. I haven’t done a thorough investigation why this doesn’t happen, but I think this is a bug in the WPF framework. The framework has a lot of places where it checks if a resource is of a certain type and if it is, then it should be sealed or frozen. I wouldn’t be surprised that the framework misses such a check for media effects, because they have been added later (in v3.5 SP1). Of course, this statement is pure speculation.
The workaround is simple… Just add the Freeze attribute to all the effects that you don’t plan to modify at runtime.
I have created a sample application that illustrates the problem. When the application is started, then you can see a checkbox that has a dropshadow effect applied to it. The window also shows whether or not this effect is frozen.
As you can see the effect is not frozen, when the effect and style are defined in the resource dictionary. If you comment out the resource dictionaries from App.xaml and uncomment the effect and style resources in App.xaml, then the application looks just the same, but you’ll see that the effect is now frozen.
The application doesn’t illustrate the actual memory leak, but in a real-life application (like ours) it actually has memory leaks that can become quite large.
I guess Microsoft introduced a problem when using effect and styles in resource dictionaries. Because I didn’t investigate why it wasn’t frozen, I cannot determine whether this is a real bug or just a side-effect of some design choice.
I also understand that it might be a bad idea to define resources at the application level this way. Using themes is probably a better solution and might solve this issue. But, there are just people out there using this approach and they don’t expect this behavior. The fix is simple… Just freeze all media effects that you don’t plan to change at runtime, which helps us to fix this for our next release.