扩展方法中的Lambdas:可能的内存泄漏?

时间:2010-04-06 10:10:26

标签: c# lambda extension-methods

我刚刚使用扩展方法回答了quite simple question。但在写完之后我记得你不能从事件处理程序取消订阅lambda。

到目前为止没有大问题。但是这一切在扩展方法中如何表现?

下面是我的代码再次剪断。所以任何人都可以启发我,如果你多次调用这个扩展方法会导致无数的计时器在内存中徘徊?

我会说不,因为定时器的范围受限于此功能。所以在离开后没有其他人有这个对象的引用。我只是有点不确定,因为我们在静态类中的静态函数中。

public static class LabelExtensions
{
    public static Label BlinkText(this Label label, int duration)
    {
        System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();

        timer.Interval = duration;
        timer.Tick += (sender, e) =>
            {
                timer.Stop();
                label.Font = new Font(label.Font, label.Font.Style ^ FontStyle.Bold);
            };

        label.Font = new Font(label.Font, label.Font.Style | FontStyle.Bold);
        timer.Start();

        return label;
    }
}

更新

只是为了澄清我使用了System.Windows.Forms.Timer。所以从您的答案看来,特别是使用这个计时器类只是正确的选择因为它做了任何事情,就像我在这种情况下所期望的那样。如果您在这种情况下尝试其他计时器类,则可能会遇到Matthew found out的问题。 如果我的对象活着或不活着,也可以使用found a wayWeakReference

更新2

经过一段时间的沉睡和一点思考后,我还对我的测试人员进行了另一次更改(answer below)我在最后一行之后添加了GC.Collect()并将持续时间设置为10000。 BlinkText()几次我一直按下我的按钮2来获取当前状态并强制进行垃圾收集。并且因为在调用Stop()方法之后,似乎所有计时器都将被销毁。因此,当我的BlinkText已经离开并且计时器正在运行时,垃圾收集也不会导致任何问题。

因此,经过你所有的良好反应和更多的测试后,我可以愉快地说它只是做了它应该做的事情而不会将计时器留在记忆中,也不会在他们完成工作之前扔掉计时器。

7 个答案:

答案 0 :(得分:4)

你的假设是正确的。调用方法时将分配timer,并且最后可以进行垃圾回收。

lamba事件处理程序(假设它未在其他地方引用)也有资格进行垃圾收集。

这是静态方法和/或扩展方法的事实不会改变对象可达性的基本规则。

答案 1 :(得分:3)

您的代码是正确的,不会泄漏内存或资源,只会 ,因为您在事件处理程序中停止了Timer。如果您对//timer.Stop();发表评论,它将继续闪烁,即使您稍后再进行GC.Collect();

Timer定义一个Window来监听WM_消息,并以某种方式锚定它。当Timer停止时(Enabled = false),它会释放该窗口 staticextension method上下文不起作用。

答案 2 :(得分:2)

内存泄漏不是你的问题。垃圾收集器可以处理计时器对象。但是虽然计时器没有停止,但似乎有一个对计时器的引用,所以它不会过早丢弃。要检查这一点,从Timer派生一个MyTimer类,重写Dispose(bool),并设置一个断点。在BlinkText之后调用GC.Collect和GC.WaitForPendingFinalizers。在第二次调用之后,第一个计时器实例被处理掉。

答案 3 :(得分:1)

我刚刚按照@cornerback84的说法做了一个有趣的测试。我创建了一个带有标签和两个按钮的表单。一个按钮绑定了您的扩展方法,另一个按钮绑定了强制GC.Collect()。然后我删除了事件循环中的timer.Stop()。多次点击开始按钮真的很有趣。眨眼的间隔非常混乱。

看起来确实存在内存/资源泄漏......但是再一次,Timer是一个永不丢弃的一次性对象。

修改:...

让乐趣开始......这也可能取决于您使用的Timer

  • System.Windows.Forms.Timer =>这个 不会收集,但会正常工作 使用系统消息泵很好。如果您致电.Stop(),此对象可能有资格收集。

  • System.Timers.Timer =>这不 使用消息泵。还有很多 打电话给消息泵。这不会 搜集。如果您致电.Stop(),此对象可能有资格收集。

  • System.Threading.Timer =>这需要调用消息泵。但是这也会停在GC.Collect()上(这个版本似乎没有泄漏)

答案 4 :(得分:0)

似乎Timer会在程序退出之前保持活着状态。我试图处理Label并将其引用设置为null,但是在我退出程序之前没有收集Timer。

答案 5 :(得分:0)

你应该通过调用它的Dispose方法显式地处理Timer,或者在使用它之后通过调用Stop来隐式地处理它,这样你的实现是正确的,并且永远不会导致内存泄漏。

答案 6 :(得分:0)

在阅读完所有答案后,我才找到了一种更好的测试方法。

我通过以下方式更新了我的Extension类:

public static class LabelExtensions
{
    public static List<WeakReference> _References = new List<WeakReference>();

    public static Label BlinkText(this Label label, int duration)
    {
        Timer timer = new Timer();

        _References.Add(new WeakReference(timer));

        timer.Interval = duration;
        timer.Tick += (sender, e) =>
        {
            timer.Stop();
            label.Font = new Font(label.Font, label.Font.Style ^ FontStyle.Bold);
        };

        label.Font = new Font(label.Font, label.Font.Style | FontStyle.Bold);
        timer.Start();

        return label;
    }
}

我还创建了第二个按钮,第二个标签,并将以下代码添加到click事件中:

    private void button2_Click(object sender, EventArgs e)
    {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < LabelExtensions._References.Count; i++)
        {
            var wr = LabelExtensions._References[i];
            sb.AppendLine(i + " alive: " + wr.IsAlive);
        }

        label2.Text = sb.ToString();
    }

现在我只是按下第一个按钮几次让第一个标签闪烁。然后我按下我的第二个按钮并得到一个列表,我可以看到我的计时器是否还活着。第一次点击它们都是真的。但后来我再次点击我的第一个按钮几次,当我更新我的状态信息时,我看到第一项已经在IsAlive中出现了错误。所以我可以肯定地说,这个功能不会导致任何内存问题。