什么是避免WPF PRISM / MVVM应用程序中的内存泄漏的最佳方法

时间:2009-12-07 11:07:54

标签: wpf mvvm memory-leaks prism

我有一个基于PRISM的WPF应用程序,它使用MVVM模式。

我注意到我的视图模型,视图以及与它们相关的所有内容偶尔会在预期的生命周期之后很久就会出现。

一个泄漏涉及在属于注入服务的集合上订阅CollectionChanged,另一个涉及不在DispatcherTimer上调用Stop方法,而另一个泄漏需要清除它的项目。

我觉得使用CompositePresentationEvent可能比订阅CollectionChanged更好,但在其他场景中,我倾向于实现IDisposable并让视图在视图模型上调用Dispose方法。

但是有些东西需要告诉视图何时在视图模型上调用Dispose,当视图的复杂性增加时,它会变得更具吸引力,并且它们开始包含子视图。

您认为处理视图模型的最佳方法是什么,以确保它们不会泄漏内存?

提前致谢

伊恩

2 个答案:

答案 0 :(得分:14)

我可以告诉你,我经历过100%的痛苦。我想,我们是记忆泄漏的兄弟。

不幸的是,我在这里想到的唯一一件事与你的想法非常相似。

我们所做的是创建一个附加属性,视图可以应用于自身以将处理程序绑定到ViewModel:

<UserControl ...
             common:LifecycleManagement.CloseHandler="{Binding CloseAction}">
...
</UserControl>

然后我们的ViewModel只有一个Action类型的方法:

public MyVM : ViewModel
{
     public Action CloseAction
     {
          get { return CloseActionInternal; }
     }

     private void CloseActionInternal()
     {
          //TODO: stop timers, cleanup, etc;
     }
}

当我的close方法触发时(我们有几种方法可以做到这一点......它是TabControl UI,标签标题上有“X”,那种事情),我只是检查这个视图是否已经注册了与AttachedProperty。如果是这样,我会调用那里引用的方法。

这是一种非常迂回的方式,只需检查View的DataContext是否是IDisposable,但当时感觉更好。检查DataContext的问题是您可能有需要此控件的子视图模型。你要么必须确保你的viewmodels链接这个dispose调用或检查图中的所有视图,看看他们的datacontexts是否是IDisposable(呃)。

我觉得这里缺少一些东西。还有一些其他框架试图以其他方式缓解这种情况。您可以查看Caliburn。它有一个处理这个的系统,其中ViewModel知道所有子视图模型,这使它能够自动链接事物。特别是,有一个名为ISupportCustomShutdown的接口(我认为这就是它的名称)有助于缓解这个问题。

然而,我所做的最好的事情是确保使用Redgate Memory Profiler等良好的内存泄漏工具,帮助您可视化对象图并找到根对象。如果你能够识别出DispatchTimer问题,我想你已经在做这个了。

编辑:我忘了一件重要的事情。由DelegateCommand中的一个事件处理程序引起的潜在内存泄漏。这是关于Codeplex的一个线程解释它。 http://compositewpf.codeplex.com/WorkItem/View.aspx?WorkItemId=4065

Prism的最新版本(v2.1)已修复此问题。 (http://www.microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en)。

答案 1 :(得分:2)

到目前为止我的发现......

除PRISM,Unity,WPF和MVVM外,我们还使用Entity Framework和Xceed数据网格。内存分析是使用dotTrace完成的。

我最终在我的视图模型的基类上实现了IDisposable,并将Dispose(bool)方法声明为virtual,允许子类也有机会清理它。 当我们的应用程序中的每个视图模型从Unity获取子容器时,我们也会处理它,在我们的例子中,这确保EF的ObjectContext超出范围。这是我们内存泄漏的主要来源。

视图模型位于基本控制器类的显式CloseView(UserControl)方法中。它在视图的DataContext上查找IDisposable并在其上调用Dispose。

Xceed数据网格似乎导致泄漏的公平份额,尤其是在长时间运行的视图中。通过分配新集合来刷新数据网格的ItemSource的任何视图都应该在分配新集合之前调用现有集合上的Clear()。

小心Entity Framework并避免任何长时间运行的对象上下文。对于大型收藏品来说,这是非常不可原谅的,即使你已经删除了收藏品,如果开启了跟踪,它也会保留对收藏中每个项目的引用,即使你不再挂在它们身上。

如果您不需要更新实体,请使用MergeOption.NoTracking检索它,尤其是在绑定到集合的长期视图中。

避免使用寿命较长的视图,当它们不可见时,不要在某个区域内保留它们,这会让您感到悲伤,尤其是当它们在可见时定期刷新数据时。

在Xceed Column上使用CellContentTemplates时,不要使用动态资源,因为资源将保存对单元格的引用,这反过来会使整个视图保持活动状态。

在Xceed Column上使用CellEditor并且资源存储在外部资源字典中时,将x:Shared =“False”添加到包含CellEditor的资源中,资源将再次使用x来保存对该单元格的引用: Shared =“False”确保您每次都获得一份新的副本,旧的副本被正确删除。

将DelegateCommand绑定到Exceed数据网格中的项目时要小心,如果行上有一个绑定命令的删除按钮,请务必在关闭视图之前清除包含ItemsSource的集合。如果要刷新集合,还需要重新初始化命令,命令将保留对每行的引用。