采取预防措施以防止因添加事件句柄而导致内存泄漏

时间:2012-04-09 18:04:11

标签: c# events memory memory-leaks

我将创建一个GUI,它将动态创建控件集,并为其分配事件。我需要在运行时添加和删除这些控件。它看起来像这样:

FlowLayoutPanel.Controls.Clear();
<< add new controls, assigning Click events with += >>

我听说使用+ =分配事件处理程序会导致内存泄漏(更具体地说,在应用程序退出之前不会释放内存)。我想避免这种情况。我知道我可以写一些像How to remove all event handlers from a control这样的函数来查找所有事件处理程序并删除它们但它看起来非常复杂。

还有其他方法吗?调用Dispose有助于删除那些事件处理程序吗?你能破坏对象来强制释放他们的记忆吗?就像在C / C ++中一样?

谢谢!

PS:问题是,我不知道要分离的事件。我将创建大量标签并向其添加不同类型的onclick事件。当它清理流程布局面板时,无法知道哪个事件处理程序附加到哪个标签。

这是示例代码(_flowLP是FlowLayoutPanel) - 这个Refresh()函数在应用程序退出之前多次运行。

    private void Refresh()
    {
        Label l;
        Random rnd = new Random();

        // What code should i add here to prevent memory leaks
        _flowLP.Controls.Clear();

        l = new Label();
        l.Text = "1";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);

        l = new Label();
        l.Text = "2";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);

        l = new Label();
        l.Text = "3";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);

        l = new Label();
        l.Text = "4";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);

        l = new Label();
        l.Text = "5";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);

        l = new Label();
        l.Text = "6";
        if (rnd.Next(3) == 0) l.Click += Method1;
        if (rnd.Next(3) == 0) l.Click += Method2;
        if (rnd.Next(3) == 0) l.Click += Method3;
        _flowLP.Controls.Add(l);
    }

3 个答案:

答案 0 :(得分:3)

当您将较短寿命的事件消费者附加到较长寿的事件生产者时,这主要是一个担心。如果他们有类似的生活或与我所描述的相反,那就不是问题了。

如果您担心这一点,只需使用 - =从事件中分离。这将删除附件创建的引用,并有助于避免此类内存问题。

编辑:由于评论有点长,我会在这里发布一些跟进。当您附加到活动时,您正在做的是在活动提供者处悬挂对自己的引用。因此,举例来说,如果你有一个带有StrikesMidnight事件的Clock类,并且你从一个名为Bedtime的类中订阅了该事件,那么Bedtime说clock.StrikesMidnight += this.HandleMidnight;的实际机制就是你为时钟分配时钟。好像Clock有一个对象属性,你说clock.ObjectProperty = this;

因此,如果Bedtime类是短暂的并且超出范围,则Bedtime会出现,在Clock上挂起对它自己的引用,然后超出范围。问题是,Clock仍然有一个参考,所以即使它超出范围,垃圾收集器也不会收集睡眠时间。

...

这就是背景。在您的情况下,您正在创建一个标签并将自己的引用附加到它(通过“MethodX”处理程序)。调用refresh时,清除标签列表(意味着它们超出范围)。它们超出了范围,它们通过MethodX处理程序引用了您的类,但那又如何呢?他们有参考不会阻止他们被GC。没有人在你的代码中持有对它们的引用,因此GC将对它们进行工作,你不会泄漏内存。

答案 1 :(得分:0)

只要您处理包含的表单,所有控件都应该被垃圾收集器清理干净。

在控件上预订事件不会使控件保持活动状态,因为控件具有对处理委托的引用;代表没有对控件的引用。

事件订阅将使控件不被清除的情况是控件中的某些代码订阅包含在其中的表单实例之外的事件。例如,如果自定义组合框订阅静态类上的事件,让控件知道何时应更新其选项列表。如果自定义控件未取消此事件,则在应用程序的持续时间内,静态类事件寄存器将引用它。由于控件将引用其容器(等等),因此整个表单可能会保留在内存中。在这种情况下,控件应该在其Dispose方法中取消事件。订阅静态类或长期存在的实例类上的事件应始终引发一个红色标记,并在不再需要时显式地取消连接。

在表单中,只要所有事件都连接到表单类上的实例方法或范围由表单实例控制的对象上,那么当表单生根的对象图表时,控件将被清除。超出范围。请注意,外部类在不再需要之后不会对表单进行引用。

答案 2 :(得分:0)

我的第一个建议是不进行预优化。在尝试解决此问题之前,请确保这是一个问题。

关于Dispose():在对象被垃圾回收之前,应该使用 ONLY 来释放非托管资源。有关详细信息,请参阅http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

为防止事件处理程序保留对控件的引用,您需要让控件取消订阅它们在创建时订阅的所有事件处理程序。提示:如果您要通过GUI添加事件处理程序,请调用自动生成的代码,以便在调用FlowLayoutPanel.Controls.Clear()之前查看取消订阅的内容。干净(-er?)这样做的方法是创建自己的控件,该控件继承自所需的控件并添加一个`Cleanup()'方法,该方法取消订阅已订阅的任何事件处理程序(您应该知道哪个事件处理程序已经订阅 - 要么是因为您编写了代码,要么是为您生成的。)