我正在构建一个使用LINQ to SQL连接到SQL Server数据库的WPF应用程序。
应用程序的主窗口包含一个ListView
,其中包含一系列详细信息视图。
ItemSource
的{{1}}绑定到作为根视图模型上的属性公开的详细视图模型对象的集合。
每个细节视图模型对象组成几个ListView
属性以及公开细节模型对象的属性,该属性反过来公开UI中显示的各种数据字段。
使用ANTS内存分析器进行分析表明,泄漏的对象是详细模型对象中包含的对象,以及它们绑定到的一些UI类。 来自先前刷新的这些对象的实例不是垃圾收集的。
ANTS有一个工具,允许用户跟踪引用链,以确定保留不需要的内存的原因。当我使用它时,我发现所有显示的链都有ICommand
。因此,我删除了有问题的ICommand
,并发现了
内存泄漏消失了。
不幸的是,我需要ICommand
来实现一些重要的功能。让我感到困惑的是它首先如何引用细节模型对象 - 它们是详细视图模型对象中两个完全独立的实例变量。
这是详细视图模型对象的构造函数(对RootViewModel的引用用于连接到ICommands的一些方法中的回调。我原先怀疑这可能会导致一个循环的引用链,可能是问题的原因,但删除它似乎没有任何影响。)
ICommand
这是设置绑定的xaml - AcknowledgeAlarm是ICommand,CarDataModel是详细模型对象:
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);
}
答案 0 :(得分:10)
CanExecuteChanged
事件处理程序可能与泄漏有关。
WPF希望ICommand实现使用对事件处理程序的弱引用。您正在使用正常的.NET事件,该事件使用强引用,这可能导致此泄漏。
您创建ParameterlessCommand
实例的方式似乎暗示CanExecute
将始终为真,您根本不需要该事件。
您实际上是在任何地方解雇事件,还是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
AFAIK即使您实际上没有任何更改事件,也需要实施INotifyPropertyChanged
。
我的猜测是,解决这两个问题中的任何一个都会使泄漏消失:错误实现的CanExecuteChanged
导致从视图模型到视图的强引用,这正是缺少{{1}的情况导致泄密。
但解决这两个问题是个好主意;不只是其中之一。
答案 1 :(得分:2)
程序员在使用MVVM时必须处理的一个“新”挑战是ViewModel不会自动清理自己。如果设置导致视图加载的属性,则更改该属性,旧对象不一定会自动处理。在清理之前,它可能会在GC上停留很长时间。
使用LINQ构建ViewModel集合尤其诱人,但您必须非常小心。 LINQ将在每次调用时返回对象的新实例,可能会造成内存泄漏和状态问题。
所有这一切,我没有在这个问题中看到足够的信息来帮助您确定泄漏的来源(以及太多无关的信息)。您最好的选择是使用Factory或类似的东西来标准化您的ViewModel创建模式,这样您只需在生成时生成ViewModel的新实例,以便您可以跟踪实例并根据需要终止它们。