Attached Behaviors Memory Leaks

“Behavior is the base class for providing attachable state and commands to an object. The types the Behavior can be attached to can be controlled by the generic parameter. Override OnAttached() and OnDetaching() methods to hook and unhook any necessary handlers from the AssociatedObject.”
If you using of the behaviors or trigger actions and these subscribe internally on events you’re in trouble. The memory used by them is never released. And could held lot of objects including views in memory causing memory leaks.

“When you subscribe to an event the event source ends up with a hard reference to the event handler. This creates a situation where the event handler cannot be cleaned up as long as the event source exists.”
So to unhook events you probably write code in OnDetaching methods however a behavior might not detach when you expect it to and vice versa, leaving added event handler on the control to survive GC. OnDetaching is only called when you explicitly remove behaviour.
The solution:
The OnAttached is called when XAML parser parses XAML and creates instance of behaviour and adds this to BehaviorCollection of target control which is exposed as DependencyAttached property. However when view is disposed, the collection (Behavior collection) was disposed of, it will never trigger OnDetaching method. If the behaviour is not properly cleanup it will not be collected by GC and will also hold BehaviorCollection and other behaviors in that collection. The behaviours are designed to extend AssociatedObject, as long as you are subscribing to AssociatedObject events its fine as the AssociatedObject (publisher) will die and your behaviour will be collected by garbage collector.

Use BehaviorBase (see code below) to avoid memory leak from behaviours. The same technique can also be used for triggers.
Drive all your behaviors from BehaviorBase class and override OnSetup and OnCleanup methods. OnSetup is triggered when behaviour explicitly is attached to already loaded object at runtime or when object is loaded.
BehaviorBase

 
public abstract class BehaviorBase<T> : Behavior<T> where T : FrameworkElement
{
private bool _isSetup = true;
private bool _isHookedUp;
private WeakReference _weakTarget;

protected virtual void OnSetup() {}
protected virtual void OnCleanup() {}
protected override void OnChanged()
{
       var target = AssociatedObject;
       if (target != null)
       {
              HookupBehavior(target);
       }
       else
       {
              UnHookupBehavior();
       }
}

private void OnTarget_Loaded(object sender, RoutedEventArgs e) { SetupBehavior(); }

private void OnTarget_Unloaded(object sender, RoutedEventArgs e) { CleanupBehavior(); }

private void HookupBehavior(T target)
{
       if (_isHookedUp) return;
       _weakTarget = new WeakReference(target);
       _isHookedUp = true;
       target.Unloaded += OnTarget_Unloaded;
       target.Loaded += OnTarget_Loaded;
       SetupBehavior();
}

private void UnHookupBehavior()
{
       if (!_isHookedUp) return;
       _isHookedUp = false;
       var target = AssociatedObject ?? (T)_weakTarget.Target;
       if (target != null)
       {
              target.Unloaded -= OnTarget_Unloaded;
              target.Loaded -= OnTarget_Loaded;
       }
       CleanupBehavior();
}

private void SetupBehavior()
{
       if (_isSetup) return;
       _isSetup = true;
       OnSetup();
 }

private void CleanupBehavior()
{
       if (!_isSetup) return;
       _isSetup = false;
       OnCleanup();
}
}

7 thoughts on “Attached Behaviors Memory Leaks

  1. Hello, Rajnish.
    Can you explain couple of moments about that implementation?
    1. Why initial state of field “_isSetup” is FALSE. In my case when I first come into OnChanged() method target already loaded, and then we go into SetupBehavior – that method quickly return as “_isSetup” is FALSE, so my overridden OnSetup methods is never called.
    2. In UnHookupBehavior we can meet situation when AssociatedObject is NULL. Does that mean, that from overridden OnCleanup it is not safe to work with AssociatedObject property? And if it is NULL I can not from OnCleanup unsubscribe from events, that I was subscribed in OnSetup method?

    I change implementation for my needs to avoid that risks, but I want to hear your opinion, especially about number 1.

    Thank you in advance!

    Like

  2. I made the same changes as Denis. But in order to unhook the events when the window is closed I had to subscribe with the dispatcher shutdownstarted event.

    private void SetupBehavior()
    {
    if (_isSetup) return;
    _isSetup = true;
    Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;
    OnSetup();
    }

    private void CleanupBehavior()
    {
    if (!_isSetup) return;
    _isSetup = false;
    Dispatcher.ShutdownStarted -= Dispatcher_ShutdownStarted;
    OnCleanup();
    }

    void Dispatcher_ShutdownStarted(object sender, EventArgs e)
    {
    OnCleanup();
    }

    Like

  3. Thanks for this! Works amazingly well – though I did have to change the _isSetup to equal FALSE, in the first line declaration in the class, to get things to work (else it would never call the first SetupBehavior. Past that, it works great! Fixed a massive memory leak for us.

    Like

  4. Wait. If I’m registering only to AssociatedObject events, so according to what you say I don’t need to -= from all those events in my OnDeaching. So why everybody is doing that in each behavior they write?

    Like

  5. you do have to unregister from events and keep that as good practice to avoid memory leaks. the above code ensures that onCleanup method is invoked while UI unloads your object..

    Like

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 )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s