自定义控件中取消订阅事件的模式

6

我正在开发一个自定义控件,它内部订阅了Touch.FrameReported - 一个静态事件。这可能会导致内存泄漏(在某些情况下确实会出现)。

这是我的当前解决方案。在Loaded / Unloaded事件中订阅/取消订阅。然而,我发现Unloaded事件并不总是被调用。这可能会导致内存泄漏。

// 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;
                  };
    }

有没有已知的模式来解决这个问题?在自定义控件“拆除”时取消订阅事件?我已经尝试过:
  • 在未加载时取消订阅。并不能总是被调用。
  • Dispose。无法使用,因为用户可能不确定地调用Dispose。
  • 弱事件。很好,但是许多实现不适用于WinRT / Silverlight,或者它们需要显式注销,或者它们只在事件被调用时注销(当然!这是一个弱事件)!
  • 终结器。如果有类似事件处理程序的GC根,则终结器会被阻止吗?

1
你能确定在什么特定情况下会导致Unloaded事件不被触发吗? - jmcilhinney
1个回答

2
如果您同时使用#1(卸载时取消订阅)和#3(弱事件监听器)的组合,则我认为您的控件不应该因任何内存泄漏而受到责备。没有更多你可以做的了。
实现IDisposable并不能真正帮助,因为没有人想在UI元素上调用“Dispose”,而且在那些“Unloaded”未被调用的情况下,要求“Dispose”只会把问题推迟到以后。而且,如果静态事件将您的控件作为其调用列表的一部分保留,那么最终器也不会被调用。
我的理解是,“Unloaded”应该在您的控件从可视树中移除时被调用。因此,在明显应该触发“Unloaded”但没有触发的情况下,要么是框架控件中存在错误(这似乎是可能的),要么是您的用户代码存在错误,导致控件的容器无法卸载。无论哪种情况,您的控件都不会是内存泄漏的来源。
使用弱事件处理程序可能是一个很好的安全保障 - 如果唯一对其的引用是弱事件侦听器,则允许您的控件被GC'd(因此,这将防止您的"FrameReported"侦听器导致内存泄漏)。我理解您的观点是有关实现方面的 - 它似乎非常棘手,但原则上这种技术没有问题(正如您可能知道的那样,框架将其用作绑定的事件侦听器)。

好主意,但我看到的WeakEvent实现(例如WinRT中的这个)需要显式注销,或者在事件实际触发时进行惰性注销。这使它们在我的上下文中变得“非弱”。Touch.FrameReported事件只有在用户触摸触摸屏幕时才会触发,如果控件不在屏幕上,则无法触发。 - Dr. Andrew Burnett-Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接