【发布时间】:2012-09-26 21:14:58
【问题描述】:
我正在使用 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 组件存在任何已知问题,以下是我的每个用户控件中使用的所有组件类型的列表:
选择编辑面板:
- 面板
- 标签
- 按钮
- 文本框
- 工具提示
节点编辑面板
- 选择编辑面板
- 流布局面板
- 面板
- 标签
- 按钮
- 文本框
- 工具提示
我还在为一些文本框使用i00SpellCheck 库
.NET Memory Profiler 最初报告的可能问题:
我让我的应用程序显示 50 个左右的 NodeEditPanel,两次,第二个列表与第一个列表具有相同的值,但是是不同的实例。 .Net Memory Profiler 比较了应用程序在第一次和第二次操作后的状态,并生成了以下可能问题列表:
-
直接事件处理程序根
一种类型具有直接由 EventHandler 根植的实例。这可能表明 EventHandler 没有被正确删除。 调查以下类型以获取更多信息。工具提示
-
已处理的实例
2 种类型的实例已被处置但未 GC。 调查以下类型以获取更多信息。System.Drawing.Graphics、WindowsFont
-
未部署的实例(释放资源)
6 种类型的实例已被垃圾收集而没有正确处理。 调查以下类型以获取更多信息。System.Drawing.Bitmap、System.Drawing.Font、System.Drawing.Region、Control.FontHandleWrapper、光标、WindowsFont
-
直接代表根
2 种类型具有直接由委托创建的实例。这可能表明委托没有被正确删除。 调查以下类型以获取更多信息。系统.__过滤器,__过滤器
-
固定实例
2 种类型具有固定在内存中的实例。 调查以下类型以获取更多信息。System.Object, System.Object[]
-
间接事件处理程序根
53 种类型具有由 EventHandler 间接根植的实例。这可能表明 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组,21056个重复字节),Hashtable(2组,20148个重复字节),(...)
-
空弱引用
WeakReference 类型的实例不再存在。 调查 WeakReference 类型以获取更多信息。System.WeakReference
-
未处理的实例(明确引用)
一种类型的实例已被垃圾收集而没有正确处理。 调查以下类型以获取更多信息。事件处理程序列表
-
大型实例
2 种类型的实例位于大对象堆中。 调查以下类型以获取更多信息。Dictionary.DictionaryItem[], System.Object[]
-
持有重复的实例
25 种类型具有由其他重复实例持有的重复实例(136 组,371,766 个重复字节)。 调查以下类型以获取更多信息。System.IO.MemoryStream(8 组,305,340 个重复字节),System.Byte[](7 组,248,190 个重复字节),PropertyStore.ObjectEntry[](10 组,40,616 个重复字节),Hashtable.bucket[] (2组,9696个重复字节),System.String(56组,8482个重复字节),EventHandlerList.ListEntry(6组,4072个重复字节),List(6组,4072个重复字节),EventHandlerList(3组,3992个重复字节) bytes), System.EventHandler (6 组, 3,992 重复字节), DialogueEditor.Choice[] (6 组, 3,928 重复字节), (...)
【问题讨论】:
-
没有必要将事件注销到自身或子控件(如 button.click)。 UserControl(或任何对象)注册到具有更长寿命的外部对象时的正常泄漏情况。结果是该对象的事件表指向 UserControl... 并使其保持活动状态。 (例如,如果您的 UC 注册了它的父对话框的“关闭”或其他什么。)
-
我怀疑您额外的 Dispose 代码毫无意义,但会显示更多代码...例如,显示控件如何/何时实例化并添加到 FlowPanel 的代码在哪里?
-
我更新了我的问题,添加了更多代码和其他信息。 ToolTip 似乎是保持对我的 UserControls 的引用的对象。我不知道为什么。可能是因为它的 Draw 事件是由操作系统处理的
-
Dispose 与托管内存使用无关。这是关于处置非托管资源,如数据库连接。
-
这个问题已经变得很大了。既然您已经了解了更多,也许您应该在一个新问题中缩小范围?
标签: c# winforms visual-studio-2010 .net-4.0 visual-c#-express-2010