我花了最近几天试图在我的WPF应用程序中追踪内存泄漏,我想我已经把它钉在了ContextMenu上。
如果用户打开上下文菜单(在我的应用程序中,这需要删除视图/ viewModel以便在删除之前始终发生),那么即使删除了对ViewModel的所有引用,并且View不再在VisualTree中,对象在内存分析器中保留了一些引用,并且数据未被释放 - 导致我的内存泄漏。
我做了一个包含ViewModel ExampleViewModel的小例子:
public class ExampleViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name
{
get { return name; }
set {
name = value;
OnPropertyChanged("Name");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
有一个很好的简单视图和&文本菜单:
<UserControl x:Class="ContextMenuMemoryLeak.ExampleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ContextMenuMemoryLeak"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TextBox Text="{Binding Name}">
<TextBox.ContextMenu>
<ContextMenu>
<TextBlock Text="Text"/>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</UserControl>
窗口将这些添加并删除到ObservableCollection:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
DataContext = this;
ViewModels = new ObservableCollection<ExampleViewModel>();
}
private ObservableCollection<ExampleViewModel> viewModels;
public ObservableCollection<ExampleViewModel> ViewModels
{
get { return viewModels; }
set {
viewModels = value;
OnPropertyChanged("ViewModels");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10; i++)
{
ViewModels.Add(new ExampleViewModel { Name = $"Item {i}" });
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
ViewModels.Clear();
}
}
并显示它们:
<Window x:Class="ContextMenuMemoryLeak.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:ContextMenuMemoryLeak"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Click="Button_Click">Add More</Button>
<Button Click="Button_Click_1">Remove All</Button>
<ListBox ItemsSource="{Binding ViewModels}">
<ListBox.ItemTemplate>
<DataTemplate>
<local:ExampleView DataContext="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
如果我在添加一些ExampleViewModel之后在内存分析器中拍摄快照,然后再次删除它后,我会得到很好的行为。 10个类被彻底删除,因为它们没有被引用到任何地方:
然而,现在我将重新添加10.然后我将右键单击其中的5个(打开上下文菜单),然后将它们全部删除:
5 View和ViewModels我打开了一个上下文菜单...无限期!
我真的很惊讶我设法在这么简单的例子中实现了这一点(我确信这是一种疯狂的方式,我在上下文菜单或其他东西中做绑定),因为我肯定能够谷歌类似的问题如果这对所有上下文菜单都如此通用......似乎并不是因为我似乎无法在谷歌上找到这个问题的很多/任何引用。
关于如何解决它的任何想法?我是否必须处理上下文菜单?
编辑:当我检查保留的ViewModel上的“路径到root”时,结果是一个弹出窗口,并在下面的上下文菜单中显示:
然而,有时它还有更多:
该视图在一些UI组件中被引用,但我似乎无法在那里找到任何有趣的东西(虽然如果我删除了绑定,但是视图会被破坏!):