正确处理和删除对 UserControl 的引用,以避免内存泄漏 [英] Properly disposing of, and removing references to UserControls, to avoid memory leak

查看:35
本文介绍了正确处理和删除对 UserControl 的引用,以避免内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Visual c# express 2010 在 c# 中开发 Windows 窗体应用程序 (.NET 4.0).我无法释放分配给我不再使用的 UserControl 的内存.

I'm developing a Windows Forms application (.NET 4.0) in c# using Visual c# express 2010. I'm having trouble freeing up memory allocated to UserControls I'm no-longer using.

我有一个 FlowLayoutPanel,其中显示了自定义用户控件.FlowLayoutPanel显示搜索结果等,因此显示的UserControls集合必须反复更新.

I have a FlowLayoutPanel, where custom UserControls are displayed. The FlowLayoutPanel displays search results and so on, so the collection of UserControls that are displayed must be repeatedly updated.

在创建和显示这些 UserControl 的每个新集合之前,对当前包含在我的 FlowLayoutPanel 的 ControlCollection(控件属性)中的所有控件调用 Dispose(),然后在同一个 ControlCollection 上调用 Clear().

Before every new set of these UserControls are created and displayed, Dispose() is called on all the Controls currently contained in my FlowLayoutPanel's ControlCollection (Controls property), then Clear() is called on the same ControlCollection.

这似乎不足以处理 UserControls 使用的资源,因为对于创建并添加到我的 ControlCollection 的每组新 UserControls,我的 UserControls 似乎也没有被垃圾收集声明. 应用程序的内存使用量在短时间内急剧上升,然后达到平稳状态,直到我显示另一个列表.我还使用 .NET Memory Profiler 分析了我的应用程序,它报告了许多可能的内存泄漏(请参阅下面的部分.)

This doesn't seem to be sufficient to dispose of the resources used by the UserControls because with each new set of UserControls which is created and added to my ControlCollection, nor do my UserControls seem to be claimed by garbage collection. The application's memory usage climbs sharply over a short period of time, then reaches a plateau until I display another list. I've also analysed my application with .NET Memory Profiler, which reports a number of possible memory leaks (See lower section.)

我错了.问题是由于使用 foreach 构造迭代 ControlCollection 并在其控件上调用 Dispose() 而导致的错误,Hans Passant 在他的回答中对此进行了描述.

该问题似乎是由我的用户控件中使用的工具提示引起的.当我删除这些时,我的 UserControls 似乎被垃圾收集声明了..NET 内存分析器证实了这一点.我之前测试中的问题 1 和 6(见下部分)不再出现​​,它报告了一个新问题:

The problem seems to be caused by ToolTip used in my UserControls. When I removed these, my UserControls appeared to be claimed by garbage collection. This was confirmed by .NET memory profiler. Problem 1 and 6 from my earlier test (see lower section) no longer appeared and it reported a new problem:

未处理的实例(释放资源并移除外部引用)7 种类型的实例被垃圾收集而没有正确处理.调查以下类型以获取更多信息.

Undisposed instances (release resource and remove external references) 7 types have instances that have been garbage collected without being properly disposed. Investigate the types below for more information.

ChoiceEditPanel(继承)、NodeEditPanel(继承)、Button、FlowLayoutPanel、Label、>Panel、TextBox

ChoiceEditPanel (inherited), NodeEditPanel (inherited), Button, FlowLayoutPanel, Label, > Panel, TextBox

即使工具提示的参考消失了,这不是一个长期的解决方案,当我不再需要它们时,仍然存在确定性地处理我的 UserControl 的问题.但是,这并不像删除对工具提示的引用那么重要.

Even with the ToolTip's reference gone, which isn't a long-term solution, there is still the problem of deterministically disposing of my UserControls when I no longer need them. However, isn't as important as removing the references to the ToolTips.

我使用名为 NodesDisplayPanel 的 UserControl,它充当 FlowLayoutPanel 的包装器.这是我的 NodesDisplayPanel 类中的方法,用于清除 FlowLayoutPanel 中的所有控件:

I use a UserControl called NodesDisplayPanel which acts as a wrapper to a FlowLayoutPanel. Here is the method in my NodesDisplayPanel class which is used to clear all Controls from my FlowLayoutPanel:

public void Clear() {
    foreach (Control control in flowPanel.Controls) {
        if (control != NodeEditPanel.RootNodePanel) {
            control.Dispose();
        }
    }
    flowPanel.Controls.Clear();
    // widthGuide is used to control the widths of the Controls below it,
    // which have Dock set to Dockstyle.Top
    widthGuide = new Panel();
    widthGuide.Location = new Point(0, 0);
    widthGuide.Margin = new Padding(0);
    widthGuide.Name = "widthGuide";
    widthGuide.Size = new Size(809, 1);
    widthGuide.TabIndex = 0;
    flowPanel.Controls.Add(widthGuide);
}

这些方法用于添加控件:

These methods are used to add Controls:

public void AddControl(Control control) {
    flowPanel.Controls.Add(control);
}
public void AddControls(Control[] controls) {
    flowPanel.Controls.AddRange(controls);
}

这里是实例化新 NodeEditPanel 并通过我的 NodesDisplayPanel 将它们添加到我的 FlowLayoutPanel 的方法.此方法来自 ListNodesPanel(如下面的屏幕截图所示),它是实例化和添加 NodeEditPanel 的几个 UserControl 之一:

Here is the method that instantiates new NodeEditPanels and adds them to my FlowLayoutPanel, via my NodesDisplayPanel. This method is from ListNodesPanel (as seen in screenshot below), one of several UserControls that instantiate and add NodeEditPanels:

public void UpdateNodesList() {
    Node[] nodes = Data.Instance.Nodes;
    Array.Sort(nodes,(IComparer<Node>) comparers[orderByDropDownList.SelectedIndex]);
    if ((listDropDownList.SelectedIndex == 1)
        && (nodes.Length > numberOfNodesNumUpDown.Value)) {
        Array.Resize(ref nodes,(int) numberOfNodesNumUpDown.Value);
    }
    NodeEditPanel[] nodePanels = new NodeEditPanel[nodes.Length];
    for (int index = 0; index < nodes.Length; index ++) {
        nodePanels[index] = new NodeEditPanel(nodes[index]);
    }
    nodesDisplayPanel.Clear();
    nodesDisplayPanel.AddControls(nodePanels);
}

这是我为 ListNodesPanel UserControl 自定义的初始化方法.希望它能让 UpdateNodesList() 方法更清晰一点:

This is my custom innitilization method for my ListNodesPanel UserControl. Hopefully it will make the UpdateNodesList() method a bit clearer:

private void NonDesignerInnitialisation() {
    this.Dock = DockStyle.Fill;
    listDropDownList.SelectedIndex = 0;
    orderByDropDownList.SelectedIndex = 0;
    numberOfNodesNumUpDown.Enabled = false;
    comparers = new IComparer<Node>[3];
    comparers[0] = new CompareNodesByID();
    comparers[1] = new CompareNodesByNPCText();
    comparers[2] = new CompareNodesByChoiceCount();
}

如果特定的 Windows.Forms 组件存在任何已知问题,以下是我的每个 UserControl 中使用的所有组件类型的列表:

In case there are any known issues with particular Windows.Forms Components, Here's a list of all the types of Components that are used in each of my UserControls:

选择编辑面板:

  • 面板
  • 标签
  • 按钮
  • 文本框
  • 工具提示

节点编辑面板

  • 选择编辑面板
  • FlowLayoutPanel
  • 面板
  • 标签
  • 按钮
  • 文本框
  • 工具提示

我也在使用 i00SpellCheck 一些文本框的库

I am also using the i00SpellCheck library for some of the TextBoxes

我让我的应用程序显示 50 个左右的 NodeEditPanel,两次,第二个列表的值与第一个列表相同,但实例不同..Net Memory Profiler 在第一次和第二次操作后比较了应用程序的状态,并产生了以下可能的问题列表:

I got my application to display 50 or so NodeEditPanels, twice, the second list having identical values to the first but being different instances. .Net Memory Profiler compared the states of the application after the first and second operation, and produced this list of possible problems:

  1. 直接事件处理程序根
    一种类型具有直接以 EventHandler 为根的实例.这可能表明尚未正确删除 EventHandler.调查以下类型以获取更多信息.

  1. Direct EventHandler roots
    One type has instances that are directly rooted by an EventHandler. This can indicate that an EventHandler has not been properly removed. Investigate the type below for more information.

工具提示

已处理的实例
2 种类型具有已处理但未 GC 的实例.调查以下类型以获取更多信息.

Disposed instances
2 types have instances that have been disposed but not GCed. Investigate the types below for more information.

System.Drawing.Graphics、WindowsFont

System.Drawing.Graphics, WindowsFont

未处理的实例(释放资源)
6 种类型的实例已被垃圾收集而没有正确处理.调查以下类型以获取更多信息.

Undisposed instances (release resource)
6 types have instances that have been garbage collected without being properly disposed. Investigate the types below for more information.

System.Drawing.Bitmap、System.Drawing.Font、System.Drawing.Region、Control.FontHandleWrapper、Cursor、WindowsFont

System.Drawing.Bitmap, System.Drawing.Font, System.Drawing.Region, Control.FontHandleWrapper, Cursor, WindowsFont

直接委托根
2 种类型具有直接以委托为根的实例.这可能表示未正确删除委托.调查以下类型以获取更多信息.

Direct delegate roots
2 types have instances that are directly rooted by a delegate. This can indicate that the delegate has not been properly removed. Investigate the types below for more information.

系统.__过滤器,__过滤器

System.__Filters, __Filters

固定实例
2 种类型具有固定在内存中的实例.调查以下类型以获取更多信息.

Pinned instances
2 types have instances that are pinned in memory. Investigate the types below for more information.

System.Object, System.Object[]

System.Object, System.Object[]

间接事件处理程序根
53 种类型具有由 EventHandler 间接根的实例.这可能表明尚未正确删除 EventHandler.调查以下类型以获取更多信息.

Indirect EventHandler roots
53 types have instances that are indirectly rooted by an EventHandler. This can indicate that the EventHandler has not been properly removed. Investigate the types below for more information.

, ChoiceEditPanel, NodeEditPanel, ArrayList, Hashtable, Hashtable.bucket[], Hashtable.KeyCollection, Container, Container.Site, EventHandlerList, (...)

, ChoiceEditPanel, NodeEditPanel, ArrayList, Hashtable, Hashtable.bucket[], Hashtable.KeyCollection, Container, Container.Site, EventHandlerList, (...)

未处理的实例(内存/资源利用率)
3 种类型的实例已被垃圾收集而没有正确处理.调查以下类型以获取更多信息.

Undisposed instances (memory/resource utilization)
3 types have instances that have been garbage collected without being properly disposed. Investigate the types below for more information.

System.IO.BinaryReader、System.IO.MemoryStream、UnmanagedMemoryStream

System.IO.BinaryReader, System.IO.MemoryStream, UnmanagedMemoryStream

重复实例
71 种类型具有重复实例(492 个集合,741,229 个重复字节).重复的实例会导致不必要的内存消耗.调查以下类型以获取更多信息.

Duplicate instances
71 types have duplicate instances (492 sets, 741,229 duplicated bytes). Duplicate instances can cause unnecessary memory consumption. Investigate the types below for more information.

GPStream(8 组,318,540 重复字节)、PropertyStore.IntegerEntry[](24 组,93,092 重复字节)、PropertyStore(10 组,53,312 重复字节)、PropertyStore.SizeWrapper(16 组、41,232 重复字节)、PropertyStore.PaddingWrapper(8 组,38,724 个重复字节),PropertyStore.RectangleWrapper(28 组,32,352 个重复字节),PropertyStore.ColorWrapper(13 组,30,216 个重复字节),System.Byte[](3 组,25,622 个重复字节),工具提示.TipInfo(10 组,21,056 个重复字节),Hashtable(2 组,20,148 个重复字节),(...)

GPStream (8 sets, 318,540 duplicated bytes), PropertyStore.IntegerEntry[] (24 sets, 93,092 duplicated bytes), PropertyStore (10 sets, 53,312 duplicated bytes), PropertyStore.SizeWrapper (16 sets, 41,232 duplicated bytes), PropertyStore.PaddingWrapper (8 sets, 38,724 duplicated bytes), PropertyStore.RectangleWrapper (28 sets, 32,352 duplicated bytes), PropertyStore.ColorWrapper (13 sets, 30,216 duplicated bytes), System.Byte[] (3 sets, 25,622 duplicated bytes), ToolTip.TipInfo (10 sets, 21,056 duplicated bytes), Hashtable (2 sets, 20,148 duplicated bytes), (...)

空弱引用
WeakReference 类型具有不再存在的实例.调查 WeakReference 类型以获取更多信息.

Empty weak reference
The WeakReference type has instances that are no longer alive. Investigate the WeakReference type for more information.

System.WeakReference

System.WeakReference

未处理的实例(明确引用)
一种类型的实例已被垃圾收集而没有正确处理.调查以下类型以获取更多信息.

Undisposed instances (clear references)
One type has instances that have been garbage collected without being properly disposed. Investigate the type below for more information.

EventHandlerList

EventHandlerList

大型实例
2 种类型具有位于大对象堆中的实例.调查以下类型以获取更多信息.

Large instances
2 types have instances that are located in the large object heap. Investigate the types below for more information.

Dictionary.DictionaryItem[], System.Object[]

Dictionary.DictionaryItem[], System.Object[]

持有重复的实例
25 种类型具有由其他重复实例持有的重复实例(136 个集合,371,766 个重复字节).调查以下类型以获取更多信息.

Held duplicate instances
25 types have duplicate instances that are held by other duplicate instances (136 sets, 371,766 duplicated bytes). Investigate the types below for more information.

System.IO.MemoryStream(8 组,305,340 重复字节),System.Byte[](7 组,248,190 重复字节),PropertyStore.ObjectEntry[](10 组,40,616 重复字节),Hashtable.bucket[](2 组,9,696 重复字节),System.String(56 组,8,482 重复字节),EventHandlerList.ListEntry(6 组,4,072 重复字节),List(6 组,4,072 重复字节),EventHandlerList(3 组,3,992 重复字节)字节),System.EventHandler(6 组,3,992 个重复字节),DialogueEditor.Choice[](6 组,3,928 个重复字节),(...)

System.IO.MemoryStream (8 sets, 305,340 duplicated bytes), System.Byte[] (7 sets, 248,190 duplicated bytes), PropertyStore.ObjectEntry[] (10 sets, 40,616 duplicated bytes), Hashtable.bucket[] (2 sets, 9,696 duplicated bytes), System.String (56 sets, 8,482 duplicated bytes), EventHandlerList.ListEntry (6 sets, 4,072 duplicated bytes), List (6 sets, 4,072 duplicated bytes), EventHandlerList (3 sets, 3,992 duplicated bytes), System.EventHandler (6 sets, 3,992 duplicated bytes), DialogueEditor.Choice[] (6 sets, 3,928 duplicated bytes), (...)

推荐答案

foreach (Control control in flowPanel.Controls) {
    if (control != NodeEditPanel.RootNodePanel) {
        control.Dispose();
    }
}
flowPanel.Controls.Clear();

这是一个非常经典的Winforms bug,很多程序员都被它咬过.处置一个控件也会将其从父级的 Control 集合中删除.大多数 .NET 集合类在迭代它们更改集合时会触发 InvalidOperationException,但 ControlCollection 类没有这样做.效果是你的 foreach 循环跳过元素,它只处理偶数控件.

This is a pretty classic Winforms bug, many programmers have been bitten by it. Disposing a control also removes it from the parent's Control collection. Most .NET collection classes trigger an InvalidOperationException when iterating them changes the collection but that wasn't done for the ControlCollection class. The effect is that your foreach loop skips elements, it only disposes the even-numbered controls.

您已经发现了问题,但是通过调用 Controls.Clear() 使问题变得更糟.特别讨厌,因为垃圾收集器不会最终确定以这种方式删除的控件.创建控件的本机窗口句柄后,它将保持由将窗口句柄映射到控件的内部表引用.只有销毁本机窗口才会从该表中删除引用.这在这样的代码中从未发生过,调用 Dispose() 是一项艰巨的要求.在 .NET 中非常不寻常.

You already discovered the problem, but made it considerably worse by calling Controls.Clear(). Extra-specially nasty because the garbage collector will not finalize the controls that are removed that way. After the native window handle for a control is created, it will stay referenced by an internal table that maps Window handles to controls. Only destroying the native window removes the reference from that table. That never happens in code like this, calling Dispose() is a rock hard requirement. Very unusual in .NET.

解决方案是向后迭代 Controls 集合,以便处置控件不会影响您迭代的内容.像这样:

The solution is to iterate the Controls collection backwards so that disposing controls doesn't affect what you iterate. Like this:

for (int ix = flowPanel.Controls.Count-1; ix >= 0; --ix) {
    var ctl = flowPanel.Controls[ix];
    if (ctl != NodeEditPanel.RootNodePanel) ctl.Dispose();
}

这篇关于正确处理和删除对 UserControl 的引用,以避免内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆