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

时间:2012-10-16 16:28:52

标签: c# wpf linq

我正在构建一个使用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);
    }

2 个答案:

答案 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的新实例,以便您可以跟踪实例并根据需要终止它们。