我正在使用Visual c#express 2010在c#中开发Windows窗体应用程序(.NET 4.0)。我无法释放分配给UserControls的内存我不再使用。< / p>
我有一个FlowLayoutPanel,其中显示了自定义UserControls。 FlowLayoutPanel显示搜索结果等,因此必须重复更新显示的UserControl集合。
在创建和显示每个新的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,&gt;面板,TextBox
即使工具提示的引用已经消失,这不是一个长期的解决方案,但当我不再需要时,仍然存在确定性地处理我的UserControls的问题。但是,删除对工具提示的引用并不重要。
我使用名为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);
}
这是实例化新NodeEditPanels并通过我的NodesDisplayPanel将它们添加到我的FlowLayoutPanel的方法。此方法来自ListNodesPanel(如下面的屏幕截图所示),是实例化和添加NodeEditPanels的几个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组件存在任何已知问题,请在此处列出我的每个UserControl中使用的所有组件类型:
ChoiceEditPanel:
NodeEditPanel
我也在使用i00SpellCheck库来处理一些TextBox
我的应用程序显示了50个左右的NodeEditPanels,两次,第二个列表与第一个列表具有相同的值但是是不同的实例。 .Net Memory Profiler在第一次和第二次操作之后比较了应用程序的状态,并生成了这个可能出现问题的列表:
直接EventHandler根
一种类型具有直接由EventHandler生根的实例。这可能表示尚未正确删除EventHandler。
请查看以下类型以获取更多信息。
工具提示
处置实例
2种类型具有已处置但未GCed的实例。
请查看以下类型以获取更多信息。
System.Drawing.Graphics,WindowsFont
未分配的实例(释放资源)
6种类型的实例在没有妥善处理的情况下进行了垃圾收集。
请查看以下类型以获取更多信息。
System.Drawing.Bitmap,System.Drawing.Font,System.Drawing.Region,Control.FontHandleWrapper,Cursor,WindowsFont
直接委托根 2种类型具有直接由委托生根的实例。这可能表示代理未被正确删除。 请查看以下类型以获取更多信息。
系统.__过滤器,__过滤器
固定的实例
2种类型具有固定在内存中的实例。
请查看以下类型以获取更多信息。
System.Object,System.Object []
间接EventHandler根
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套,21,056个重复字节),Hashtable(2套,20,148个重复字节),(...)
清空弱引用
WeakReference类型具有不再存活的实例。
调查WeakReference类型以获取更多信息。
System.WeakReference
未分配的实例(明确的引用)
一种类型的实例在没有正确处理的情况下被垃圾收集。
请查看以下类型以获取更多信息。
EventHandlerList
大型实例
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组,9,696个重复字节),System.String(56组,8,482个重复字节),EventHandlerList.ListEntry(6组,4,072个重复字节),List(6组,4,072个重复字节),EventHandlerList(3组,3,992个重复) bytes),System.EventHandler(6套,3,992个重复字节),DialogueEditor.Choice [](6套,3,928个重复字节),(...)
答案 0 :(得分:20)
foreach (Control control in flowPanel.Controls) {
if (control != NodeEditPanel.RootNodePanel) {
control.Dispose();
}
}
flowPanel.Controls.Clear();
这是一个非常经典的Winforms错误,许多程序员都被它咬了。处置控件也会将其从父级的Control集合中删除。大多数.NET集合类在迭代它们时会触发InvalidOperationException更改集合但是ControlCollection类没有这样做。结果是你的foreach循环跳过元素,它只处理偶数控件。
您已经发现了问题,但通过调用Controls.Clear()使问题变得更糟。特别令人讨厌,因为垃圾收集器不会最终确定以这种方式删除的控件。在创建控件的本机窗口句柄之后,它将保持由将窗口句柄映射到控件的内部表引用。仅销毁本机窗口会从该表中删除引用。在类似的代码中永远不会发生这种情况,调用Dispose()是一项艰难的要求。在.NET中很不寻常。
解决方案是向后迭代Controls集合,以便处理控件不会影响您迭代的内容。像这样:
for (int ix = flowPanel.Controls.Count-1; ix >= 0; --ix) {
var ctl = flowPanel.Controls[ix];
if (ctl != NodeEditPanel.RootNodePanel) ctl.Dispose();
}