我正在使用 Visual c# express 2010 在 c# 中开发 Windows 窗体应用程序 (.NET 4.0)。我无法释放分配给我不再使用的 UserControls 的内存。
问题:
我有一个 FlowLayoutPanel,其中显示了自定义用户控件。FlowLayoutPanel显示搜索结果等,所以显示的UserControls集合必须反复更新。
在创建和显示每组新的 UserControl 之前,对当前包含在我的 FlowLayoutPanel 的 ControlCollection(Controls 属性)中的所有控件调用 Dispose(),然后在同一个 ControlCollection 上调用 Clear()。
这似乎不足以处理 UserControls 使用的资源,因为每组新的 UserControls 创建并添加到我的 ControlCollection 中,垃圾回收似乎也没有声明我的 UserControls。应用程序的内存使用量在短时间内急剧上升,然后达到一个平台,直到我显示另一个列表。我还使用.NET Memory Profiler分析了我的应用程序,它报告了许多可能的内存泄漏(参见下半部分。)
我认为出了什么问题:
我错了。问题是由于使用 foreach 构造遍历 ControlCollection 并在其控件上调用 Dispose() 导致的错误,Hans Passant 在他的回答中对此进行了描述。
该问题似乎是由我的 UserControls 中使用的 ToolTip 引起的。当我删除这些时,我的 UserControls 似乎被垃圾收集所占用。.NET 内存分析器证实了这一点。我之前的测试中的问题 1 和 6(见下部分)不再出现,它报告了一个新问题:
未处理的实例(释放资源并删除外部引用) 7 种类型的实例已被垃圾回收而未正确处理。调查以下类型以获取更多信息。
ChoiceEditPanel(继承)、NodeEditPanel(继承)、Button、FlowLayoutPanel、Label、>Panel、TextBox
即使工具提示的参考消失了,这不是一个长期的解决方案,当我不再需要它们时,仍然存在确定性地处置我的用户控件的问题。但是,它不如删除对工具提示的引用重要。
代码和更多细节
我使用一个名为 NodesDisplayPanel 的 UserControl,它充当 FlowLayoutPanel 的包装器。这是我的 NodesDisplayPanel 类中的方法,用于从我的 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);
}
这些方法用于添加控件:
public void AddControl(Control control) {
flowPanel.Controls.Add(control);
}
public void AddControls(Control[] controls) {
flowPanel.Controls.AddRange(controls);
}
这是实例化新 NodeEditPanel 并通过我的 NodesDisplayPanel 将它们添加到我的 FlowLayoutPanel 的方法。此方法来自 ListNodesPanel(如下面的屏幕截图所示),它是实例化和添加 NodeEditPanel 的几个 UserControl 之一:
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() 方法更清晰一些:
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 组件存在任何已知问题,以下是我的每个用户控件中使用的所有组件类型的列表:
选择编辑面板:
- 控制板
- 标签
- 按钮
- 文本框
- 工具提示
节点编辑面板
- 选择编辑面板
- 流布局面板
- 控制板
- 标签
- 按钮
- 文本框
- 工具提示
我还在为一些 TextBoxes使用i00SpellCheck库
.NET Memory Profiler 最初报告的可能问题:
我让我的应用程序两次显示 50 个左右的 NodeEditPanel,第二个列表与第一个列表具有相同的值,但它们是不同的实例。.Net Memory Profiler 比较了应用程序在第一次和第二次操作后的状态,并生成了以下可能问题列表:
直接 EventHandler 根
一种类型具有直接由 EventHandler 根的实例。这可能表明 EventHandler 没有被正确删除。调查以下类型以获取更多信息。工具提示
已处置实例
2 种类型具有已处置但未 GC 的实例。调查以下类型以获取更多信息。System.Drawing.Graphics,WindowsFont
未处理的实例(释放资源)
6 种类型的实例已被垃圾回收而没有正确处理。调查以下类型以获取更多信息。System.Drawing.Bitmap、System.Drawing.Font、System.Drawing.Region、Control.FontHandleWrapper、光标、WindowsFont
直接委托根
2 种类型具有由委托直接根植的实例。这可能表明委托没有被正确删除。调查以下类型以获取更多信息。系统.__过滤器,__过滤器
固定实例
2 类型具有固定在内存中的实例。调查以下类型以获取更多信息。系统对象,系统对象 []
间接事件处理程序根
53 种类型具有由事件处理程序间接根的实例。这可能表明 EventHandler 没有被正确删除。调查以下类型以获取更多信息。, ChoiceEditPanel, NodeEditPanel, ArrayList, Hashtable, Hashtable.bucket[], Hashtable.KeyCollection, Container, Container.Site, EventHandlerList, (...)
未处理的实例(内存/资源利用率)
3 种类型的实例已被垃圾回收而没有正确处理。调查以下类型以获取更多信息。System.IO.BinaryReader、System.IO.MemoryStream、UnmanagedMemoryStream
重复实例
71 种类型有重复实例(492 组,741,229 个重复字节)。重复的实例会导致不必要的内存消耗。调查以下类型以获取更多信息。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 个重复字节),ToolTip.TipInfo( 10 组,21,056 个重复字节),Hashtable(2 组,20,148 个重复字节),(...)
空弱引用
WeakReference 类型的实例不再存在。调查 WeakReference 类型以获取更多信息。System.WeakReference
未处理的实例(清除引用)
一种类型的实例已被垃圾回收而没有正确处理。调查以下类型以获取更多信息。事件处理程序列表
大型实例
2 类型具有位于大型对象堆中的实例。调查以下类型以获取更多信息。Dictionary.DictionaryItem[], System.Object[]
持有的重复实例
25 种类型具有由其他重复实例持有的重复实例(136 组,371,766 个重复字节)。调查以下类型以获取更多信息。System.IO.MemoryStream(8组,305340个重复字节),System.Byte[](7组,248190个重复字节),PropertyStore.ObjectEntry[](10组,40616个重复字节),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 个重复字节),(...)