Behaviors, Triggers and memory leaks in Silverlight
Recently I was investigating strange memory leaks in Silverlight application. After switching back and forth through the screens the memory was gradually growing. Because of the lack of silverlight profiler I used windbg to diagnose the problem. It turned out that none of the view models and controls were released.
After several hours of digging I found that the problem was the behaviors and triggers from Blend samples (Expression.Samples.Interactivity). If you remove controls that use some of the behaviors or trigger actions (these which subscribe internally on events) you’re in trouble. The memory used by them is never released. If your application is small there is probably no problem. However if you’re developing more complicated ones with lot of screens that have to work for several hours the memory consumption can be unacceptable.
The solution
Here is the workaround I used to correct this situation. Before removing any control you should detach all triggers and behaviors.
static void UnwireBehaviors(object current) { DependencyObject parent = current as DependencyObject; if (parent != null) { Interaction.GetTriggers(parent).Clear(); Interaction.GetBehaviors(parent).Clear(); for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); UnwireBehaviors(child); } } }
However it's not all. There is a couple of bugs in the code from codeplex too. The class BindingListener doesn't call Detach on it's internal structures and doesn't wire events in some cases. The corrected code should look something like this:
public class BindingListener { [...] private void Detach() { if (this.listener != null) { this.listener.Detach(); // This is not called in the original samples this.ReturnListener(); } } private DependencyPropertyListener GetListener() { DependencyPropertyListener listener; if (BindingListener.freeListeners.Count != 0) { listener = BindingListener.freeListeners[BindingListener.freeListeners.Count - 1]; BindingListener.freeListeners.RemoveAt(BindingListener.freeListeners.Count - 1); // return listener; This was called in the original samples preventing event wiring } else listener = new DependencyPropertyListener(); listener.Changed += this.HandleValueChanged; return listener; } [...] }