Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern to unsubscribe events in CustomControls

I am developing a custom control, which internally subscribes to Touch.FrameReported - a static event. This has the potential to cause a memory leak (and in some case, it does).

This is my current solution. To subscribe / unsubscribe in the Loaded / Unloaded event. However, I'm finding that the Unloaded event is not always called. This can lead to memory leaks.

// Imagine this is a CustomControl, to be consumed by users 
// with no regard for calling Dispose
public class CustomGrid : Grid
{   
    public CustomGrid()
    {
        Loaded += (s, a) =>
                  {
                      Touch.FrameReported -= OnTouchFrameReported;
                      Touch.FrameReported += OnTouchFrameReported;
                  };

        Unloaded += (s, a) =>
                  {
                    // The intention is to unsubscribe on unload, which should pre-date
                    // user intended 'disposal' of the control
                    Touch.FrameReported -= OnTouchFrameReported;
                  };
    }

Is there a known pattern to solve this? Unsubscribing to events in a Custom Control 'tear-down'? I have already tried:

  • Unsubscribe in unloaded. Doesn't always get called.
  • Dispose. Cannot be used because a user may not deterministically call Dispose
  • Weak Events. Nice, but many implementations are unsuitable for WinRT/Silverlight, or, they require explicit deregistering, or, they only de-register if the event is called (Duh! Its a weak-event)!
  • Finalizer. Doesn't Finalizer get prevented if there are GC roots like event handlers?
like image 496
Dr. Andrew Burnett-Thompson Avatar asked Jul 28 '14 22:07

Dr. Andrew Burnett-Thompson


1 Answers

tl;dr If you use a combination of #1 (unsubscribe on unload) and #3 (weak event listener), then I don't think that your control should be at fault for any memory leaks. There is nothing more you can do.

Implementing IDisposable doesn't really help, because no-one wants to call "Dispose" on UI elements, and anyway, in those instances where "Unloaded" isn't getting called, requiring "Dispose" would just kick the can down the road. And you're right, the finalizer would not be called if there is a static event holding on to your control as part of its invocation list.

My understanding is that "Unloaded" should be called when your control is removed from the Visual Tree. So in cases where "Unloaded" is not triggered where it definitely should have been, then either there is a bug in a framework control somewhere (which does seem to be a possibility), or there is a bug in your users' code that is preventing your control's container from being unloaded. In either case, your control would not be the source of the memory leak.

Using a weak event handler is probably a good fail-safe -- it allows your control to be GC'd if the only references to it are weak event listeners (hence, this would prevent your "FrameReported" listener from causing a memory leak). I understand your point about implementations -- it seems very tricky to implement, but there is nothing wrong with the technique in principle (as you probably know, the framework uses it as the event listener for bindings).

like image 143
McGarnagle Avatar answered Oct 24 '22 07:10

McGarnagle