我想要找回我的记忆!如何真正地释放控制?

7

我正在制作一个应用程序,创建了大量的窗口控件(按钮、标签等),它们都是通过函数动态创建的。我遇到的问题是,当我移除这些控件并释放它们的内存时,它们并没有从内存中彻底移除。

void loadALoadOfStuff()
{
    while(tabControlToClear.Controls.Count > 0)
        tabControlToClear.Controls[0].Dispose();
    //I even put in:
    GC.Collect();
    GC.WaitForPendingFinalizers();
    foreach(String pagename in globalList)
        tabControlToClear.Controls.Add(MakeATab(pagename));
}

TabPage MakeATab(string tabText)
{
    TabPage newT = new MakeATab();
    newT.Text = tabText;
    //Fill page with controls with methods like this
    return newT;
}

由于某些原因,这个过程并没有将我的内存还回来,所以当该过程运行5次时,我就会遇到内存违规的情况。虽然我对对象和控件释放不太熟悉,但是在广阔的网络上寻找仍未得到任何指示,如果您有任何想法,我将不胜感激。

更新:我一直在观察用户对象的创建和销毁(任务管理器),发现我创建了一个选项卡页,添加了一个单击处理程序,添加了一个面板,添加了两个带有单击处理程序、工具提示和背景图像的按钮(I think this is where the problem is)。应用程序说它创建了8个新项目,但是当我运行我的dispose时,我只从内存中删除了4个。我一直试图删除事件处理程序,但似乎没有任何区别。

已解决!当我向面板添加新项时,我给它们传递了一个工具提示(很傻,但我正在学习)。 对于其他有同样问题的人(感谢下面评论和指导),我发现为了使控件真正释放(如我错误地表述的那样):

1:如果您有工具提示,请确保它可以被访问!不要像我一样做!例如:

这是错误的!

TabPage MakeATab(string tabText)
{
    TabPage newT = new MakeATab();
    ToolTip myTip = new ToolTip();
    newT.Text = tabText;
    //Fill page with controls with methods like this
    myTip.SetToolTip(newT, "Something to say");
    return newT;
}

如果你这样做,就会失去对工具提示的指针,由于工具提示不是连接到对象的子元素(相反,工具提示对控件有强引用),即使你销毁了控件,你也无法访问保留对象的工具提示。
2:在任何操作之前,请调用toolTip.RemoveAll()。这将删除所有与控件的关联。请注意,如果您正在为其他控件使用此提示,则它们刚刚失去了其工具提示。
3:从基本control.ControlCollection中删除任何内部控件(如果它们使用非托管内存,我猜测。我这样做是因为它使我的应用程序工作得很好...)
4:删除任何自定义事件处理程序。
5:最后,释放该对象。我编写了一个快速递归函数,它可以很好地完成这项工作。
    private void RecursiveDispose(Control toDispose)
    {
        while (toDispose.Controls.Count > 0)
            RecursiveDispose(toDispose.Controls[0]);

        if (toDispose.BackgroundImage != null)
            BackgroundImage = null;

        if (toDispose.GetType() == typeof(Button))
            toDispose.Click -= [Your Event];
        else if (toDispose.GetType() == typeof(TabPage))
            toDispose.DoubleClick -= [Your Event];
        else if (toDispose.GetType() == typeof(Label))
            toDispose.MouseMove -= [Your Event];

        toDispose.Dispose();
    }

这种方法非常粗糙,可能有更好的方式来解决问题,但是除非有人能够想出更好的方法,否则这将是必须采用的。感谢大家的帮助,你们可能刚刚拯救了我的整个项目。


我更进一步,甚至为所有项目创建了一个递归的dispose方法,但是...没有内存回收。 private void RecursiveDispose(Control toDispose) { while (toDispose.Controls.Count > 0) RecursiveDispose(toDispose.Controls[0]); toDispose.Dispose(); }这样做可以清除所有可见的项目并清空控件数组,但我仍然没有得到任何内存回收。 - user248634
1
有其他原因导致了您的内存错误;通常情况下,您不需要调用垃圾收集器。您的实际代码是什么? - Noon Silk
是的,我刚刚注意到有大量用户对象的积累。但我的代码只删除其中的一些。例如,当我运行创建时,它会产生8个对象,但是当我释放这些对象时,仅清除了其中4个... - user248634
我应该提一下,这不是一个干净的50%损失,有时候我建造11个项目,清除6个。这很奇怪。 - user248634
至于我的实际代码,它有点大,无法在这里放置。 - user248634
4个回答

6

您还需要清除引用。

while(tabControlToClear.Controls.Count > 0)
{ 
    var tabPage = tabControlToClear.Controls[0];
    tabControlToClear.Controls.RemoveAt(0);
    tabPage.Dispose(); 

    // Clear out events.

    foreach (EventHandler subscriber in tabPage.Click.GetInvocationList())
    {
        tabPage.Click -= subscriber;
    }
}

1
好的观点,尽管OP没有提到任何事件订阅。忘记事件处理程序分配是“内存泄漏”的常见原因。 - Jim L
混乱,可能我听起来有点愚笨(我是新手,求你宽容一下),但那段代码不起作用。 - user248634
5
也许在取消事件处理程序后应该调用 "tabPage.Dispose();"? - lmsasu

4
在这个代码块中,你调用了Dispose但没有删除引用:
while(tabControlToClear.Controls.Count > 0)
    tabControlToClear.Controls[0].Dispose();

你需要移除控件的所有引用(包括在控件集合中的引用、任何已注册的事件处理程序以及其他可能存在的引用),才能使控件有资格进行垃圾回收。


谢谢提供信息,我已经为各种控件分配了一些处理程序,但是如何创建递归清除函数呢?我在一个选项卡控件中添加了一个面板,其中嵌套了按钮和标签,所以嵌套非常严重。我如何处理对象及其处理程序的处理,而不知道类型呢? - user248634
还有,我已经将工具提示附加到这些控件上了,它们需要被处理吗? - user248634

0
void loadALoadOfStuff()
{
    while(tabControlToClear.Controls.Count > 0)
        tabControlToClear.Controls[0].Dispose();
    //I even put in:
    GC.Collect();
    GC.WaitForPendingFinalizers();
    foreach(String pagename in globalList)
        tabControlToClear.Controls.Add(MakeATab(pagename));
}

在我的看法中,您正在重新分配测试方法结束时的所有标签实例,因此首先处理它们没有任何好处。跳过最后两行,看看是否有帮助。


该函数的目的是去除所有控件,释放它们的内存,并用新的控件替换它们。如果删除最后两行,我就失去了编写该函数的原因,即在处理旧的、不需要的对象后填充页面,而不仅仅是处理它们。 - user248634
如果你提到了你的内存使用量增加,我会建议像ChaosPandion所说的那样。然而,我假设你抱怨的是一直很高的内存使用量。 - Johannes Rudolph
不,我正在动态创建超过1000个用户控件...当它们被清除时,我需要那些内存空间回来... - user248634

0

到目前为止,对于这个问题已经有很多很好的答案提到了一个可能的原因,即对象没有被释放,这些都是值得检查的事情。

然而,当我遇到这种问题时,我会使用内存分析器来帮助跟踪对象的引用,上一次我看内存分析器时,ANTS Memory Profiler(来自RedGate)是最好的之一。(他们提供14天的试用期,足够调查单个问题。)

这就是使用弱事件模式的原因之一,但这将是一个全新的问题

(当您动态修改UI时,UI对象的生命周期是一个雷区,任务管理器很好地检查了您的代码是否按预期工作)

据我所发现,用户控制管理比我最初想象的要复杂得多。(该死的.NET让我产生了一种错误的理解感觉)。 我认为其中一个问题可能是我愚蠢地认为将工具提示分配给控件会以某种方式将工具提示对象“放入”控件中,因此当控件被处理时将其删除,但不是这样的。微软却把它变成了一个奇怪的类,封装了这个对象,因此即使我已经删除了按钮并尝试销毁它,(我不再有指向它的指针),但工具提示仍然保留着这个对象....这是相当了不起的... - user248634
@Psytechnic,WinForms存在很多问题,因为它是一个托管包装器,围绕着非托管的User32 API,但是它仍然比直接从C/C++中使用User32(或MFC)要好得多。WPF要好得多,因为它从一开始就被设计用于从托管代码中使用。 - Ian Ringrose
你会建议将整个应用程序重写为WPF应用程序吗? - user248634
@Psytechnic,重写代码需要花费大量资金,但如果你刚开始编写应用程序,那么除了学习WPF的成本之外,转向WPF可能是一个不错的选择。你也可以在同一个应用程序中混合使用WPF和Winforms。预计需要花费几个星期的时间来充分学习WPF! - Ian Ringrose
我似乎已经解决了这个项目的问题,这样可以避免昂贵的重写,但现在我知道了WPF和托管控件,下一个项目我一定会专注于此。所以现在我要尽可能地玩转WPF。感谢你们的帮助。 - user248634

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接