【问题标题】:ICommand binding causing UI memory leak in WPF applicationICommand 绑定导致 WPF 应用程序中的 UI 内存泄漏
【发布时间】:2012-10-16 16:28:51
【问题描述】:

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

应用程序的主窗口包含一个ListView,其中包含一系列详细视图。 ItemSourceItemSource 绑定到作为根视图模型上的属性公开的详细视图模型对象的集合。 每个详细视图模型对象都由几个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> . . .

【问题讨论】:

  • 我怀疑这可能与 ParameterlessCommand 中的 CanExecuteChanged 事件有关。你能说明它是如何实现的吗?
  • 它是 public event EventHandler CanExecuteChanged;, protected virtual void OnCanExecuteChanged(EventArgs args) { if (CanExecuteChanged != null) { CanExecuteChanged(this, args); } } - 抱歉格式化,似乎无法添加返回

标签: c# wpf linq


【解决方案1】:

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


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

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

【讨论】:

    猜你喜欢
    • 2012-12-21
    • 2011-08-17
    • 2010-11-27
    • 2011-07-08
    • 2019-10-21
    • 2011-03-21
    • 2018-03-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多