妥善处置,并删除引用用户控件,以避免内存泄漏控件、妥善、内存、用户

2023-09-09 21:03:32 作者:妳愛她也愛我

我正在开发一个Windows窗体应用程序(.NET 4.0)在C#中使用Visual C#EX preSS 2010年我无法腾出分配到我没有再使用用户控件的内存。

的问题:

我有一个FlowLayoutPanel的,其中会显示自定义的用户控件。所述FlowLayoutPanel的显示搜索结果等,所以所显示的用户控件的收集必须重复更新。

在每一个新的集合,这些用户控件的创建和显示的Dispose()调用的所有当前包含在我的FlowLayoutPanel中的ControlCollection(Controls属性),然后清除()调用相同的ControlCollection。

控件

似乎这不是足以处理所用的用户控件的资源,因为每一个新组被创建并添加到我的ControlCollection用户控件中,也没有我的用户控件似乎声称垃圾收集应用程序的内存使用量大幅攀升在很短的时间周期,然后达到一个平台,直到我显示另一个列表。我也分析了我的应用程序 .NET内存分析器,该报告的一些可能的内存泄漏(参见下段。)

我觉得是哪里错了什么:

我错了。 的问题是由于使用一个foreach结构遍历的ControlCollection和调用Dispose(),它的控制,这汉斯帕桑特描述了他的答案中的错误。

这个问题似乎是在我的用户控件工具提示使用造成的。当我删除了这些,我的用户控件似乎是声称垃圾收集。这是由.NET内存分析器证实。问题1和6从我前面的测试(参见下段)不再出现​​,它报道了新的问题:

  

未予处置情况下(释放资源,消除外部参考)   7种有已垃圾回收没有被妥善处理实例。   调查以下类型的详细信息。

     

ChoiceEditPanel(继承),NodeEditPanel(继承),巴顿,FlowLayoutPanel的,标签,>面板,文本框

即使有工具提示的参考不见了,这不是一个长期的解决方案,仍有确定性的处置我的用户控件,当我不再需要他们的问题。然而,这不是去掉了引用工具提示一样重要。

code和详细信息

我使用了一个名为NodesDisplayPanel用户控件充当一个包装到FlowLayoutPanel的。下面是这是用来清除所有控件从我的FlowLayoutPanel的在我NodesDisplayPanel类中的方法:

 公共无效清除(){
    的foreach(在flowPanel.Controls控制控制){
        如果(控制!= NodeEditPanel.RootNodePanel){
            control.Dispose();
        }
    }
    flowPanel.Controls.Clear();
    // widthGuide用于控制其下面的控制的宽度,
    //其中有码头设置为Dockstyle.Top
    widthGuide =新面板();
    widthGuide.Location =新点(0,0);
    widthGuide.Margin =新填充(0);
    widthGuide.Name =widthGuide;
    widthGuide.Size =新的大小(809,1);
    widthGuide.TabIndex = 0;
    flowPanel.Controls.Add(widthGuide);
}
 

这些方法用于添加控件:

 公共无效的AddControl(控制控制){
    flowPanel.Controls.Add(控制);
}
公共无效AddControls(控制[]控件){
    flowPanel.Controls.AddRange(对照);
}
 
Chrome开发者工具之JavaScript内存分析

下面是实例化新NodeEditPanels并将它们添加到我的FlowLayoutPanel的,通过我的NodesDisplayPanel的方法。这个方法是从ListNodesPanel(如在下面的截图所示),多个用户控件用于实例和添加NodeEditPanels之一:

 公共无效UpdateNodesList(){
    节点[]节点= Data.Instance.Nodes;
    的Array.Sort(节点,(IComparer的&其中;节点&GT)comparers [orderByDropDownList.SelectedIndex]);
    如果((listDropDownList.SelectedIndex == 1)
        &功放;&安培; (nodes.Length> numberOfNodesNumUpDown.Value)){
        Array.Resize(参考节点,(INT)numberOfNodesNumUpDown.Value);
    }
    NodeEditPanel [] nodePanels =新NodeEditPanel [nodes.Length]
    对于(INT指数= 0;指数< nodes.Length;指数++){
        nodePanels [指数] =新NodeEditPanel(节点[指数]);
    }
    nodesDisplayPanel.Clear();
    nodesDisplayPanel.AddControls(nodePanels);
}
 

这是我的自定义innitilization方法我ListNodesPanel用户控件。希望这将使UpdateNodesList()方法的更清楚一点:

 私人无效NonDesignerInnitialisation(){
    this.Dock = DockStyle.Fill;
    listDropDownList.SelectedIndex = 0;
    orderByDropDownList.SelectedIndex = 0;
    numberOfNodesNumUpDown.Enabled = FALSE;
    comparers =新的IComparer&其中;节点> [3];
    comparers [0] =新CompareNodesByID();
    comparers [1] =新CompareNodesByNPCText();
    comparers [2] =新CompareNodesByChoiceCount();
}
 

在万一有任何已知问题特别Windows.Forms的成分,这里的组件的所有在我的每一个用户控件使用的类型的列表:

ChoiceEditPanel:

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

NodeEditPanel

ChoiceEditPanel FlowLayoutPanel的 面板 标签 按钮 在文本框 工具提示

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

最初报道的.NET内存分析器

可能的问题:

我有我的应用程序,以显示50左右NodeEditPanels,两次,有相同价值观的第一,但是是不同的情况下,第二个列表。达网络存储器探查相比,第一和第二操作之后,应用程序的状态,以及所产生的可能问题列表:

直接事件处理程序的根 一种类型具有直接根由事件处理程序的实例。这可以表明,一个EventHandler尚未正确移除。 调查以下类型的详细信息。

工具提示

处置情况 2类型有已处理完毕,但没有GCed实例。 调查以下类型的详细信息。

System.Drawing.Graphics,WindowsFont

未予处置情况下(释放资源) 6种有已垃圾回收没有被妥善处理实例。 调查以下类型的详细信息。

System.Drawing.Bitmap,System.Drawing.Font,System.Drawing.Region,Control.FontHandleWrapper,光标,WindowsFont

直接委托根 2类型有直接植根由委托实例。这可以表明,代表没有被正确移除。 调查以下类型的详细信息。

系统.__过滤器,__Filters

寄托实例 2类型有寄托在内存中的实例。 调查以下类型的详细信息。

System.Object的,System.Object的[]

间接事件处理程序的根 53种具有间接扎根由事件处理程序的实例。这可以指示该事件处理程序没有被正确移除。 调查以下类型的详细信息。

,ChoiceEditPanel,NodeEditPanel,ArrayList中,哈希表,Hashtable.bucket [],Hashtable.KeyCollection,集装箱,Container.Site,EventHandlerList,(...)

未予处置情况(内存/资源利用率​​) 3类型有已垃圾回收没有被妥善处理实例。 调查以下类型的详细信息。

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

重复的实例 71种有重复的实例(492套,741229复制字节)。重复的情况下,可能会导致不必要的内存消耗。 调查以下类型的详细信息。

GPStream(8套,318540复制字节),PropertyStore.IntegerEntry [](24套,93092复制字节),PropertyStore(10套,53312复制字节),PropertyStore.SizeWrapper(16套,41,232复制字节),PropertyStore .PaddingWrapper(8套,38724复制字节),PropertyStore.RectangleWrapper(28套,32352复制字节),PropertyStore.ColorWrapper(13套,30216复制字节),System.Byte [](3套,25622复制字节),工具提示.TipInfo(10套,21,056复制字节),哈希表(2套,20148复制字节),(...)

空弱引用 该WeakReference的类型有不再活着的实例。 调查的WeakReference类型以获取更多信息。

System.WeakReference

未予处置情况(明确提到) 一类具有已被垃圾回收没有被妥善处理实例。 调查以下类型的详细信息。

EventHandlerList

大型实例 2类型有位于大对象堆中的实例。 调查以下类型的详细信息。

Dictionary.DictionaryItem [],System.Object的[]

举行重复的实例 25种具有其他重复的实例(136套,371766复制字节)持有的重复的实例。 调查以下类型的详细信息。

System.IO.MemoryStream(8套,305340复制字节),System.Byte [](7套,248190复制字节),PropertyStore.ObjectEntry [](10套,40616复制字节),Hashtable.bucket [] (2套,9696复制字节),System.String(56套,8482复制字节),EventHandlerList.ListEntry(6套,4,072复制字节),表(6套,4,072复制字节),EventHandlerList(3台,3992复制字节),System.EventHandler(6套,3992复制字节),DialogueEditor.Choice [](6套,复制3,928字节),(...)

解决方案

 的foreach(控制flowPanel.Controls控制){
    如果(控制!= NodeEditPanel.RootNodePanel){
        control.Dispose();
    }
}
flowPanel.Controls.Clear();
 

这是一个pretty的经典的WinForms的错误,很多程序员都被咬伤了。处置的控制也从父母的控制集合中删除。大多数.NET集合类触发一个InvalidOperationException迭代时,他们改变了收集,但,这不是对的ControlCollection类来完成。其效果是,你的foreach循环跳过元件,它仅处置偶数对照

您已经发现了这个问题,但使它大大致电Controls.Clear差()。特特别讨厌的,因为垃圾收集器将无法完成被移除的方式控制。创建本地窗口句柄的控制后,将通过保持一个映射窗口句柄来控制内部表引用。只有破坏了原生窗口会删除该表中的参考。这永远不会发生在code这样,调用Dispose()是一个坚硬的岩石要求。非常不寻常的.NET。

解决的办法是遍历控件集合向后,这样处理的控制不会影响你迭代。像这样的:

 的(INT九= flowPanel.Controls.Count-1;九> = 0; --ix){
    VAR CTL = flowPanel.Controls [九]
    如果(CTL = NodeEditPanel.RootNodePanel!)ctl.Dispose();
}
 

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.

The problem:

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.

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.

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.)

What I think is going wrong:

I was wrong. The problem was a bug caused by using a foreach construct to iterate through a ControlCollection and calling Dispose() on its controls, which Hans Passant describes in his answer.

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:

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 (inherited), NodeEditPanel (inherited), Button, FlowLayoutPanel, Label, > Panel, TextBox

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.

Code and more details

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);
}

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);
}

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();
}

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:

ChoiceEditPanel:

Panel Label Button TextBox ToolTip

NodeEditPanel

ChoiceEditPanel FlowLayoutPanel Panel Label Button Textbox ToolTip

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

Possible issues initially reported by .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:

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.

ToolTip

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

System.Drawing.Graphics, WindowsFont

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

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

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

System.Object, System.Object[]

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, (...)

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

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 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), (...)

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

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

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[]

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 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();

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.

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.

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();
}