Prism 的 DelegateCommand 内存泄漏
Prism's DelegateCommand memory leak
使用此代码,您可以 100% 地重现我的问题。我创建了一个 'parent' ViewModel:
using Prism.Commands;
using Prism.Mvvm;
using System.Collections.ObjectModel;
namespace WpfApplication1 {
public class ViewModel : BindableBase {
public ObservableCollection<ChildViewModel> Items { get; set; }
public DelegateCommand<ChildViewModel> CloseItem { get; set; }
public ViewModel() {
CloseItem = new DelegateCommand<ChildViewModel>(OnItemClose);
Items = new ObservableCollection<ChildViewModel>(new[] {
new ChildViewModel { Name = "asdf" },
new ChildViewModel { Name = "zxcv" },
new ChildViewModel { Name = "qwer" },
new ChildViewModel { Name = "fdgz" },
new ChildViewModel { Name = "hgjkghk" }
});
}
private void OnItemClose(ChildViewModel obj) {
Items.Remove(obj);
}
}
}
其中包含 ObservableCollection
个 ChildViewModel
个实例:
using Prism.Mvvm;
namespace WpfApplication1 {
public class ChildViewModel : BindableBase {
public string Name { get; set; }
protected byte[] leakArr = new byte[1024 * 1024 * 10];
}
}
如您所见,我在每个 ChildViewModel
中声明了一个 10mb 的数组。现在视图:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new ViewModel();
}
}
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Path=Name}" DockPanel.Dock="Left" Margin="2,3,0,2" />
<Button DockPanel.Dock="Right" BorderThickness="0" Background="Transparent"
Margin="10,2,2,2"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=DataContext.CloseItem}"
CommandParameter="{Binding}">
<Button.Content>
<TextBlock FontWeight="Bold" Text="X" />
</Button.Content>
</Button>
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Window>
查看 VS2015 内存诊断工具,我可以看到即使每个 ChildViewModel
都已从 Items
集合中删除,它仍然存在于内存中,一个对象(对于每个虚拟机)仍然持有一个引用对它 - Button
控件。有趣的是,如果我将 Button
的命令声明替换为:
Click="Button_Click"
private void Button_Click(object sender, RoutedEventArgs e) {
(DataContext as ViewModel).CloseItem.Execute(((sender as Button).DataContext as ChildViewModel));
}
没有内存泄漏 - 这意味着调试工具没有显示任何已处置的 ChildViewModel
实例。我不知道这是 Prism 特有的问题,还是某种 wpf 怪癖。
我使用的是最新版本的 .net 和 prism 库。
您是否检查自定义 ICommand 实现是否具有相同的行为?
可能是我们在删除弱引用时引入了内存泄漏,这是因为存在相关错误。
能否请您在此处报告此问题:
https://github.com/PrismLibrary/Prism/issues
我们会调查,我们会调查的。
编辑:我很高兴听到这不是 Prism 问题:)
我似乎找到了这个问题的解决方案(至少在我的应用程序中)- 我停止直接绑定到 ChildViewModel
实例,而是将 CommandParameter
绑定到 Name
属性 个 ChildViewModel
。然后,在 ViewModel
中,我只是浏览 Items 集合并删除具有匹配 属性 值的项目。现在 VS 诊断工具没有显示任何应该被 GC 而没有的对象,因此内存泄漏消失了。
Prism 团队正在分析这个问题:https://github.com/PrismLibrary/Prism/issues/345
使用此代码,您可以 100% 地重现我的问题。我创建了一个 'parent' ViewModel:
using Prism.Commands;
using Prism.Mvvm;
using System.Collections.ObjectModel;
namespace WpfApplication1 {
public class ViewModel : BindableBase {
public ObservableCollection<ChildViewModel> Items { get; set; }
public DelegateCommand<ChildViewModel> CloseItem { get; set; }
public ViewModel() {
CloseItem = new DelegateCommand<ChildViewModel>(OnItemClose);
Items = new ObservableCollection<ChildViewModel>(new[] {
new ChildViewModel { Name = "asdf" },
new ChildViewModel { Name = "zxcv" },
new ChildViewModel { Name = "qwer" },
new ChildViewModel { Name = "fdgz" },
new ChildViewModel { Name = "hgjkghk" }
});
}
private void OnItemClose(ChildViewModel obj) {
Items.Remove(obj);
}
}
}
其中包含 ObservableCollection
个 ChildViewModel
个实例:
using Prism.Mvvm;
namespace WpfApplication1 {
public class ChildViewModel : BindableBase {
public string Name { get; set; }
protected byte[] leakArr = new byte[1024 * 1024 * 10];
}
}
如您所见,我在每个 ChildViewModel
中声明了一个 10mb 的数组。现在视图:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new ViewModel();
}
}
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Path=Name}" DockPanel.Dock="Left" Margin="2,3,0,2" />
<Button DockPanel.Dock="Right" BorderThickness="0" Background="Transparent"
Margin="10,2,2,2"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=DataContext.CloseItem}"
CommandParameter="{Binding}">
<Button.Content>
<TextBlock FontWeight="Bold" Text="X" />
</Button.Content>
</Button>
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Window>
查看 VS2015 内存诊断工具,我可以看到即使每个 ChildViewModel
都已从 Items
集合中删除,它仍然存在于内存中,一个对象(对于每个虚拟机)仍然持有一个引用对它 - Button
控件。有趣的是,如果我将 Button
的命令声明替换为:
Click="Button_Click"
private void Button_Click(object sender, RoutedEventArgs e) {
(DataContext as ViewModel).CloseItem.Execute(((sender as Button).DataContext as ChildViewModel));
}
没有内存泄漏 - 这意味着调试工具没有显示任何已处置的 ChildViewModel
实例。我不知道这是 Prism 特有的问题,还是某种 wpf 怪癖。
我使用的是最新版本的 .net 和 prism 库。
您是否检查自定义 ICommand 实现是否具有相同的行为?
可能是我们在删除弱引用时引入了内存泄漏,这是因为存在相关错误。
能否请您在此处报告此问题:
https://github.com/PrismLibrary/Prism/issues
我们会调查,我们会调查的。
编辑:我很高兴听到这不是 Prism 问题:)
我似乎找到了这个问题的解决方案(至少在我的应用程序中)- 我停止直接绑定到 ChildViewModel
实例,而是将 CommandParameter
绑定到 Name
属性 个 ChildViewModel
。然后,在 ViewModel
中,我只是浏览 Items 集合并删除具有匹配 属性 值的项目。现在 VS 诊断工具没有显示任何应该被 GC 而没有的对象,因此内存泄漏消失了。
Prism 团队正在分析这个问题:https://github.com/PrismLibrary/Prism/issues/345