由于DelegateCommand导致WPF应用程序中的内存泄漏

时间:2010-06-15 10:48:29

标签: c# wpf mvvm delegatecommand

我刚刚使用MVVM模式完成了用WPF和c#编写的桌面应用程序。在这个应用程序中,我使用Delegate Command实现来包装我的ModelView中公开的ICommands属性。问题是这些DelegateCommands阻止我的ModelView和View在关闭视图后被垃圾收集。因此,在我终止整个应用程序之前,它一直保持冷静。我分析了应用程序,我发现它是关于将模型视图保存在内存中的委托命令。 我怎么能避免这种情况,这是mvvm模式的本质,还是我的模式的植入?感谢。

编辑:这是我实现MVVM模式

的一小部分但完整的部分

首先:CommandDelegte类

class DelegateCommand:ICommand
{
    private Action<object> execute;
    private Predicate<object> canExcute;
    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
        {
            throw new ArgumentNullException("execute");
        }
        this.execute = execute;
        this.canExcute = canExecute;
    }
    public bool CanExecute(object parameter)
    {
        if (this.canExcute != null)
        {
            return canExcute(parameter);
        }
        return true;
    }

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


    public void Execute(object parameter)
    {
        this.execute(parameter);
    }
}

第二:ModelView类

public class ViewModel:DependencyObject, INotifyPropertyChanged
{
    private DelegateCommand printCommand;

    public ICommand PrintCommand
    {
        get
        {
            if (printCommand == null)
            {
                printCommand = new DelegateCommand(Print, CanExecutePrint);
            }
            return printCommand;
        }
    }
    void Print(object obj)
    {
        Console.WriteLine("Print Command");

    }
    bool CanExecutePrint(object obj)
    {
        return true;
    }


    public event PropertyChangedEventHandler PropertyChanged;
    private void OnProeprtyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

第三:背后的窗口代码

public MainWindow()
    {
        InitializeComponent();
        base.DataContext = new ViewModel();
    }

Forth:我的XAML

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Window.InputBindings>
    <KeyBinding Key="P" Modifiers="Control" Command="{Binding Path=PrintCommand}"/>
</Window.InputBindings>
<StackPanel>
    <Button Content="Print - Ctrl+P" Width="75" Height="75" Command="{Binding Path=PrintCommand}"/>
</StackPanel>

3 个答案:

答案 0 :(得分:9)

在你的情况下,什么包含对什么的引用?

  1. DelegateCommand包含对ViewModel的引用 - 其executecanExecute属性包含对ViewModel实例的方法的引用。

  2. ViewModel包含对DelegateCommand - PrintCommand属性的引用。

  3. 该视图包含对ViewModel

  4. 的任意数量的引用
  5. CommandManagerDelegateCommand事件中包含对RequerySuggested的引用。

  6. 最后一个参考是一个特殊情况:CommandManagerWeakReference事件中使用了RequerySuggested,所以尽管DelegateCommand注册了该事件,它仍然可以垃圾收集。

    鉴于这一切,你应该没有问题。如果视图被释放,则ViewModelDelegateCommand都不可访问。

    您说您已对该应用进行了分析,DelegateCommand正在引用ViewModel。在我看来,合乎逻辑的下一个问题应该是:什么引用了DelegateCommand?它不应该是CommandManager。您的应用程序中是否还有其他引用命令的内容?

答案 1 :(得分:1)

阅读这篇文章之后,我偶然发现了一个有相关信息的网页。它是CodePlex上的一个名为Memory Leak caused by DelegateCommand.CanExecuteChanged Event的页面。

报告人:huetter
更新:dschenkelman

  

在分析我的应用程序时,我发现有很多EventHandler   从未被DelegateCommand取消注册   CanExecuteChanged的事件。所以那些EventHandler从未出现过   垃圾收集器,导致严重的内存泄漏。

     

注册CanExecuteChanged-EventHandles是在外面完成的   应用程序代码范围我曾预计它们会被取消注册   自动也是。在这一点上,我认为这也可能是   一个ThirdParty WPF控件问题,但进一步挖掘我读了一篇博客   帖子说“WPF需要ICommand.CanExecuteChanged-Event   为EventHandlers应用WeakReferences“。我看了一下   RoutedCommand,并注意到它也使用了WeakReferences。

     

我调整了DelegateCommand以使用类似的实现   RoutedCommand的CanExecuteChanged-Event,以及内存泄漏   不见了。 CompositeCommand也是如此。

     

2009年11月3日下午6:28关闭此问题已修复   Prism-v2.1发布,所以Workitem现在关闭了。棱镜2.1可以   从这里下载:
  http://www.microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en

答案 2 :(得分:1)

我认为在这段代码中有一个循环引用,导致ViewModel永远不会被垃圾回收。

我知道这是一个老问题,但我要指出,DelegateCommand或RelayCommand的某些实现持有对该操作的WeakReference。您在这里使用DelegateCommand是典型的,但遗憾的是这会导致内存泄漏,因为当ViewModel的方法传递给DelegateCommand的构造函数时,对包含该方法的类的引用会自动被捕获代表。

如果您在ViewModel上实现了IDispose并在Dispose中明确清除了对DelegateCommands的引用,那么您可以继续使用此实现。但是,您构建ViewModel的视图也必须使用它的Dipose。