gpt4 book ai didi

c# - 正确处理和删除对 UserControl 的引用,以避免内存泄漏

转载 作者:行者123 更新时间:2023-12-02 10:33:21 30 4
gpt4 key购买 nike

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

问题:

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

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

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

我认为出了什么问题:

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

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

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



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

代码和更多细节

我使用名为 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 组件存在任何已知问题,以下是我的每个 UserControl 中使用的所有组件类型的列表:

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

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

  • 我也在使用 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、Cursor、WindowsFont
  • 直接委托(delegate)根
    2 种类型具有直接以委托(delegate)为根的实例。这可能表示未正确删除委托(delegate)。
    调查以下类型以获取更多信息。

    System.__过滤器,__过滤器
  • 固定实例
    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 个重复字节),哈希表(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 重复字节), System.EventHandler(6 组,3,992 个重复字节),DialogueEditor.Choice[](6 组,3,928 个重复字节),(...)
  • 最佳答案

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

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

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

    解决方案是向后迭代 Controls 集合,以便处置控件不会影响您迭代的内容。像这样:
    for (int ix = flowPanel.Controls.Count-1; ix >= 0; --ix) {
    var ctl = flowPanel.Controls[ix];
    if (ctl != NodeEditPanel.RootNodePanel) ctl.Dispose();
    }

    关于c# - 正确处理和删除对 UserControl 的引用,以避免内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12610535/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com