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

发布于 2024-09-05 21:34:11 字数 2766 浏览 6 评论 0原文

我刚刚使用 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();
    }

第四:我的 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>

I just finished desktop apps written in WPF and c# using MVVM pattern. In this app I used Delegate Command implementation to wrap the ICommands properties exposed in my ModelView. The problem is these DelegateCommands prevent my ModelView and View from being garbage collected after closing the view. So it stays larking until I terminate the whole application. I profile the application I find it’s all about delegatecommand that keeping the modelview in memory.
How could I avoid this situation and is this in nature of mvvm pattern, or it’s about my implantation of the pattern?. Thanks.

Edit: this is small but complete portion of how i implement MVVM pattern

First: CommandDelegte class

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);
    }
}

Second: ModelView Class

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));
        }
    }
}

Third: Window code behind

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

Forth: My 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>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

不打扰别人 2024-09-12 21:34:11

就您而言,什么包含对什么的引用?

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

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

  3. 视图包含对 ViewModel 的任意数量的引用。

  4. CommandManager 在其 RequerySuggested 事件中包含对 DelegateCommand 的引用。

最后一个引用是一种特殊情况:CommandManager 在其 RequerySuggested 事件中使用 WeakReference,因此尽管 DelegateCommand > 注册该事件,它仍然可以被垃圾收集。

考虑到这一切,你应该不会有问题。如果视图被释放,则 ViewModelDelegateCommand 都将无法访问。

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

In your case, what contains a reference to what?

  1. DelegateCommand contains a reference to ViewModel - its execute and canExecute properties contain references to a methods of the ViewModel instance.

  2. ViewModel contains a reference to DelegateCommand - its PrintCommand property.

  3. The view contains any number of references to the ViewModel.

  4. The CommandManager contains a reference to DelegateCommand in its RequerySuggested event.

That last reference is a special case: CommandManager uses a WeakReference in its RequerySuggested event, so despite the fact that DelegateCommand registers for that event, it can still be garbage-collected.

Given all this, you shouldn't be having a problem. If the view gets disposed, neither the ViewModel nor the DelegateCommand should be reachable.

You say you've profiled the application and DelegateCommand is holding a reference to ViewModel. It seems to me that the logical next question should be: what's holding a reference to DelegateCommand? It shouldn't be CommandManager. Do you have something else in your application that's referencing your commands?

凡间太子 2024-09-12 21:34:11

我认为在此代码中存在循环引用,导致 ViewModel 永远不会被垃圾收集。

我知道这是一个老问题,但我会指出 DelegateCommand 或 RelayCommand 持有对操作的 WeakReference。此处对 DelegateCommand 的使用是典型的,但不幸的是,此实现会导致内存泄漏,因为当 ViewModel 的方法传递到 DelegateCommand 的构造函数时,委托会自动捕获对包含该方法的类的引用。

如果您在 ViewModel 上实现了 IDispose 并在 Dispose 中显式清除了对 DelegateCommands 的引用,那么您可以继续使用此实现。但是,构建 ViewModel 的视图也必须将其丢弃。

I think that in this code there is a circular reference which is causing the ViewModel to never be garbage collected.

I know this is an old question, but I will point out that some implementations of DelegateCommand or RelayCommand hold a WeakReference to the action. Your use of the DelegateCommand here is typical, but unfortunately will cause memory leaks with this implementation because when the ViewModel's method is passed into the DelegateCommand's constructor, a reference to the class containing that method is automatically captured by the delegate.

If you implemented IDispose on your ViewModel and cleared the references to the DelegateCommands explicitly in Dispose, then you could continue to use this implementation. Your view that's constructing your ViewModel would also have to Dipose of it, however.

眼藏柔 2024-09-12 21:34:11

读完这篇文章后,我发现了一个包含一些相关信息的网页。这是 CodePlex 上一个名为 DelegateCommand.CanExecuteChanged 事件导致的内存泄漏的页面。

报告人:huetter
更新者:dschenkelman

在分析我的应用程序时,我注意到有大量的事件处理程序
从未从 DelegateCommand 中注销
CanExecuteChanged 事件。所以那些事件处理程序从来没有被
垃圾收集器,导致严重的内存泄漏。

由于注册 CanExecuteChanged-EventHandles 是在外部完成的
我原以为它们会被取消注册的应用程序代码范围
自动也是如此。此时我想这也可能是
第三方 WPF 控制问题,但进一步挖掘我读了一篇博客
帖子指出“WPF 需要 ICommand.CanExecuteChanged-Event
为事件处理程序应用弱引用”。我查看了
RoutedCommand,并注意到它也使用 Wea​​kReferences。

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

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

After reading this post, I then came across a web page that had some relating information. It is a page on CodePlex called Memory Leak caused by DelegateCommand.CanExecuteChanged Event.

Reported by : huetter
Updated by : dschenkelman

When profiling my application I noticed that plenty of EventHandlers
had never been deregistered from DelegateCommand's
CanExecuteChanged-Event. So those EventHandlers were never been
garbage-collector, which caused a severe memory leak.

As registering CanExecuteChanged-EventHandles is done outside
application code scope I had expected them to be deregistered
automatically as well. At this point I thought this might as well be
a ThirdParty WPF control issue, but digging further I read a blog
post stating that "WPF expects the ICommand.CanExecuteChanged-Event
to apply WeakReferences for EventHandlers". I had a look into
RoutedCommand, and noticed it uses WeakReferences as well.

I adapted DelegateCommand to use an implementation similar to
RoutedCommand's CanExecuteChanged-Event, and the memory leak was
gone. The same is true for CompositeCommand.

Closed Nov 3, 2009 at 6:28 PM by This issue was fixed in the
Prism-v2.1 release, so the Workitem is closed now. Prism 2.1 can be
downloaded from here:
http://www.microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文