10

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

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

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

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

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

这是详细视图模型对象的构造函数(对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 是详细模型对象:

<ListView x:Name="itemGridView"Grid.Row="1"ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemsSource="{Binding CarDataViewModels}" IsSynchronizedWithCurrentItem="True" Margin="0,0,0,0">
        <ListView.ItemTemplate>
            <DataTemplate>
                </DataTemplate.Resources>
                <Button Command="{Binding AcknowledgeAlarm}">
                    <Border DataContext="{Binding CarDataModel}" BorderBrush="{StaticResource GrayFadeBrush}" Background="White" BorderThickness="5">
                        <Grid> . . .
4

1 回答 1

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为每个属性设置单独的等事件。这是因为 WPF 中处理各个属性的逻辑会在从视图模型引用回 UI 元素时导致内存泄漏:http: //support.microsoft.com/kb/938416

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


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

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

于 2012-10-18T00:10:40.887 回答