在WPF应用程序中导致UI内存泄漏的ICommand绑定

我正在构建一个使用LINQ to SQL连接到SQL Server数据库的WPF应用程序。

应用程序的主窗口包含一个包含一系列详细视图的ListViewListViewItemSource绑定到作为根视图模型上的属性公开的详细视图模型对象的集合。 每个详细视图模型对象组成多个ICommand属性以及公开详细模型对象的属性,该属性又公开UI中显示的各种数据字段。

使用ANTS内存分析器进行分析表明,泄漏的对象是详细模型对象中包含的对象,以及它们绑定到的一些UI类。 以前刷新的这些对象的实例不是垃圾回收。

ANTS有一个工具,允许用户跟踪引用链,以确定保留不需要的内存的原因。 当我使用它时,我发现所有出现的链都有一个ICommand 。 因此,我删除了有问题的ICommand ,发现内存泄漏消失了。

不幸的是,我需要ICommand来实现一些重要的function。 让我感到困惑的是它首先如何引用细节模型对象 – 它们是详细视图模型对象中的两个完全独立的实例变量。

这是详细视图模型对象的构造函数(对RootViewModel的引用用于连接到ICommands的一些方法中的回调。我原先怀疑这可能导致一个循环的引用链,这可能是导致问题,但删除它似乎没有任何影响。)

 public CarDataViewModel(CarData carDataItem, RootViewModel parentViewModel) { _parentViewModel = parentViewModel; CarDataModel = carDataItem; CompetingCheckboxStatus = CarDataModel.CurrentCar.Competing; AcknowledgeAlarm = new ParameterlessCommand(AcknowledgeAlarmClicked); Acknowledge = new ParameterlessCommand(AcknowledgeClicked); ShowReport = new ParameterlessCommand(ShowReportClicked); Cancel = new ParameterlessCommand(CancelClicked); } 

这里是设置绑定的xaml – AcknowledgeAlarm是ICommand,CarDataModel是详细模型对象:

     

CanExecuteChanged事件处理程序可能涉及泄漏。

WPF希望ICommand实现使用对事件处理程序的弱引用。 您正在使用正常的.NET事件,该事件使用强引用,这可能导致此泄漏。

您创建ParameterlessCommand实例的方式似乎暗示CanExecute将始终为true,并且您根本不需要该事件。 你实际上是在任何地方发射事件,还是OnCanExecuteChanged未使用的代码?

如果没有,请将事件定义替换为:

 public event EventHandler CanExecuteChanged { add {} remove {} } 

这样,事件不会存储任何处理程序,并且视图模型避免了对UI元素的强引用。

如果需要引发事件,最简单的解决方案是使用CommandManager.RequerySuggested ,它匹配ICommand所需的弱事件语义:

 public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } 

您应该做的另一件事是在视图模型中实现INotifyPropertyChanged (如果您还没有这样做),并使用它而不是为每个属性设置单独的NameChanged等事件。 这是因为当视图模型中的引用返回到UI元素时,WPF中处理各个属性的逻辑会导致内存泄漏: http : //support.microsoft.com/kb/938416

即使您实际上没有任何更改事件,您也需要实现INotifyPropertyChanged AFAIK。


我的猜测是修复这两个问题中的任何一个都会使泄漏消失:错误实现的CanExecuteChanged导致视图模型的强引用被查看,这正是缺少INotifyPropertyChanged导致泄漏的情况。

但解决这两个问题是个好主意; 不只是其中之一。

程序员在使用MVVM时必须处理的一个“新”挑战是ViewModel不会自动清理自己。 如果设置导致视图加载的属性,则更改该属性,旧对象不一定会自动处理。 在清理之前,它可能会长时间停留在GC上。

使用LINQ来构建ViewModel集合尤其诱人,但您必须非常小心。 LINQ将在每次调用时返回对象的新实例,可能会造成内存泄漏和状态问题。

总而言之,我没有在这个问题中看到足够的信息来帮助您确定泄漏的来源(以及太多无关的信息)。 最好的办法是使用Factory或类似的方法标准化ViewModel创建模式,这样您只需在生成时生成ViewModel的新实例,以便可以跟踪实例并根据需要终止它们。