WPF用户控件的正确清理

23

我对WPF比较陌生,其中一些东西对我来说很陌生。 首先,与Windows Forms不同,WPF控件层次结构不支持IDisposable。在Windows Forms中,如果用户控件使用了任何托管资源,则通过重写每个控件实现的Dispose方法非常容易清理资源。

在WPF中,情况并不那么简单。我已经搜索了几个小时,遇到了两个基本主题:

第一个主题是Microsoft明确表示,WPF不实现IDisposable,因为WPF控件没有非托管资源。虽然这可能是真的,但他们似乎完全忽视了用户对其WPF类层次结构的扩展可能实际上使用托管资源(直接或通过模型间接使用)。通过不实现IDisposable,Microsoft已经有效地删除了唯一可以清理自定义WPF控件或窗口所使用的非托管资源的保证机制。

其次,我发现了几个关于Dispatcher.ShutdownStarted的参考文献。我尝试使用ShutdownStarted事件,但它似乎不能为每个控件触发。我有许多WPF UserControl,我已经为ShutdownStarted实现了处理程序,但它从未被调用过。 我不确定它是否仅适用于Windows,或者可能是WPF App类。但是它没有正确触发,每次应用程序关闭时我都会泄漏打开的PerformanceCounter对象。

除了Dispatcher.ShutdownStarted事件外,是否有更好的清理非托管资源的替代方法?是否有某种技巧实现IDisposable以调用Dispose?如果可能的话,我非常希望避免使用终结器。

5个回答

13

恐怕Dispatcher.ShutdownStarted确实是WPF在UserControls中处理资源释放的唯一机制。如果有类似之前我问过的问题,可以参考一下。

解决该问题的另一种方法是,如果可能的话,将所有可释放的资源从代码后台移动到单独的类中(例如使用MVVM模式时的ViewModel)。然后在较高级别上,通过Messenger类处理主窗口关闭事件并通知所有ViewModel。

我很惊讶您没有收到Dispatcher.ShutdownStarted事件。那时您的UserControls是否附加到顶级窗口上了?


4
支持将一次性资源移出代码后端。WPF的关键学习点之一是最小化代码后端,以利用数据绑定架构的强大和表达能力。这是一个痛苦的过程(学习曲线更像是攀登悬崖),但当你“掌握”WPF思维模式时,它是非常有益的。 - Greg D
所有可丢弃的资源实际上都在 ViewModel 中,它们本身就是 IDisposable。我真的很困惑为什么 Dispatcher.ShutdownStarted 事件没有触发。性能计数器控件(及其关联的 ViewModel)确实附加到 WPF 图表中,因为它嵌入在 <Grid> 中的 <TabControl> 中。 - jrista
1
@Greg D:我通常会理解WPF模型。一旦掌握了WPF的基础知识,我就开始使用MVVM,我的CodeBehind几乎是最简单的(只有默认构造函数及其对InitializeComponent的调用)。WPF的可组合性和数据绑定能力令人惊叹,如果可以选择,我永远不会回到Windows Forms。 - jrista

11

在WPF下,IDisposable接口(几乎)没有意义,因为机制与Winforms不同。在WPF中,您必须牢记可视化和逻辑树:这是基本的。
因此,任何可视对象通常作为其他对象的子对象存在。WPF构建机制的基础是按层次结构附加可视对象,然后在它们不再有用时分离并销毁。

我认为您可以检查自UIElement以来公开的OnVisualParentChanged方法:当可视对象被附加或分离时,将调用此方法。那可能是释放非托管对象(套接字、文件等)的正确位置。


谢谢你提供关于OnVisualParentChanged的提示。我会尝试使用它,看看是否有助于解决我的问题。 - jrista

9

我也在寻找这个问题的解决方法,经过测试不同的选项后,我采用了venezia的解决方案。

protected override void OnVisualParentChanged(DependencyObject oldParent)
    {
        if (oldParent != null)
        {
            MyOwnDisposeMethod(); //Release all resources here
        }

        base.OnVisualParentChanged(oldParent);
    }

我发现当父控件调用 Children.Clear() 方法并且子控件已经添加了项目时,DependencyObject 有一个值。但是当父控件添加一个项目(Children.Add(CustomControl))而子控件为空时,DependencyObject 的值为 null。


我将其更改为 if (Parent == null),这样如果我将控件移动到另一个容器中,它就不会自毁。 - Sean

0

虽然其他人已经给出了关于这个问题非常有用的信息,但是有一些信息您可能不知道,它将解释为什么没有IDisposable。基本上,WPF(以及Silverlight)大量使用WeakReference——这使您可以引用GC仍然可以收集的对象。


谢谢你的见解,Pete。我想知道是否有一些链接可以更详细地解释这个问题?我很好奇弱引用的大量使用不会引起问题。它们在某些特定情况下可能是一个强大的工具...但我无法想象它们在WPF中如何使用。 - jrista

0

当我使用一些IDbConnection实现驱动程序或Entity Framework连接到数据库时,遇到了一些困难。

在这些情况下,建议是每个窗口保持一个对象连接/上下文,以便能够跟踪更改/事务。(link)

因此,我重写了OnClosing:

protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
      base.OnClosing(e);
      this._context.Dispose();
}

上下文可以是实现IDisposable以清理资源的ViewModel/Control。

使用OnClosing来释放资源的示例(link


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