WPF 列表框内存泄漏
Posted
技术标签:
【中文标题】WPF 列表框内存泄漏【英文标题】:WPF Listbox memory leak 【发布时间】:2017-03-01 13:31:33 【问题描述】:这是我的 xaml:
<ListBox Grid.Row="4" HorizontalAlignment="Stretch" Margin="10,132,10,10" ScrollViewer.VerticalScrollBarVisibility="Disabled" Name="lbStatus" VerticalAlignment="Stretch" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"/>
还有我的 C# 代码:
public void DisplayStatusMessage(string msg)
if (lbStatus.Dispatcher.CheckAccess())
AddMessage(msg, Brushes.Black);
else
this.Dispatcher.BeginInvoke((Action)(() =>
AddMessage(msg, Brushes.Black);
));
private void AddMessage(string msg)
ListBoxItem status = new ListBoxItem();
status.Content = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss:fff ") + msg;
lbStatus.Items.Add(status);
lbStatus.ScrollIntoView(status);
status = null;
我在 while (true) 循环中调用 DisplayStatusMessage 以在列表框上显示状态。我的应用程序在一夜之间显着增长,这似乎表明列表框上存在内存泄漏。是否有替代列表框来显示无限状态?我认为将虚拟化设置为回收可以防止泄漏?
【问题讨论】:
如果您不需要保留以前的状态,那么最好使用最近的状态保持简单。 您永远不会从 ListBox 中删除项目,那么除了不断增长的内存消耗,您还能期待什么? 除此之外,您通过直接创建 ListBoxItems(它们是“容器”)来绕过项目容器的虚拟化和回收。您应该改为创建 数据项(例如此处的字符串)并将 ListBox 的ItemTemplate
属性设置为适当的 DataTemplate。然后将 ListBox 的 ItemsSource
属性绑定到 ObservableCollection<string>
,并向该集合添加/删除消息字符串。从这里开始阅读:Data Templating Overview
【参考方案1】:
这本身不是“泄漏”。如果您不断地向ListBox
添加条目,甚至在一夜之间,您可能会有数千个条目,这当然需要内存来存储。
为避免这种情况,您可以在添加新条目时删除旧条目:
if (listbox.Items.Count > 100)
listbox.Items.RemoveAt(0); // 0 or 99, whichever is your oldest
listbox.Items.Add(status);
listbox.ScrollIntoView(status);
【讨论】:
【参考方案2】:如果分配了“DataTemplate”,ListBox 就会发生泄漏。即使您只是在其中放入一个 TextBox,您也可以测量出相当大的内存泄漏。我通过将其替换为“ListView”找到了解决方案。 “ListView”似乎已经注意到了这种泄漏,微软的解决方案是添加一个受保护的方法“ClearContainerForItemOverride”。这对不再使用的每个 ListViewItem(数据项的容器)调用。现在我们还可以使用它来清除所有分配的东西和附加的事件处理程序,这些处理程序会阻止垃圾收集器收集我们的 DataTemplate 的内容。这取决于您在 DataTemplate 中拥有的内容。所以我只是为“ListView”的客户端调用了一个公共事件,这样她/他就可以把代码放在那里。就我而言,我只有一个 TextBox,这导致了以下代码:
private void lstLog_ClearListViewItemContainer(object sender,
ClearListViewItemContainerEventArgs clearListViewItemContainerEventArgs)
ContentPresenter contentPresenter = VisualTree.FindChild<ContentPresenter>(
clearListViewItemContainerEventArgs.ListViewItem, null, true);
if (contentPresenter != null)
Control control = VisualTree.FindChild<Control>(contentPresenter, null, true);
if (control != null)
TextBox txtLog = (TextBox)control;
txtLog.GotFocus -= txtLog_GotFocus;
txtLog.PreviewMouseDown -= txtLog_PreviewMouseDown;
txtLog.SelectionChanged -= txtLog_SelectionChanged;
txtLog.ContextMenu = null; // cut the reference from the context menu back to the textbox
BindingOperations.ClearBinding(txtLog, TextBox.TextProperty);
BindingOperations.ClearBinding(txtLog, TextBox.ForegroundProperty);
txtLog.Foreground = null;
txtLog.Resources = null;
contentPresenter.DataContext = null;
contentPresenter.ContentTemplate = null;
contentPresenter.Content = null;
contentPresenter.Resources = null;
到“VisualTree”:深入搜索类型 T 的第一个子级。 .NET 'VisalTreeHelper' 的定制类型版本。
我认为并非所有的清理代码都是必要的,但太多总比太少好。内存使用量的测量表明泄漏以这种方式消失了。
【讨论】:
以上是关于WPF 列表框内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章
XP 上的 WPF 内存泄漏(CMilChannel,HWND)