本地范围定时器的预期行为是什么?

时间:2011-05-27 15:24:18

标签: c# .net timer garbage-collection

具体来说,如果在本地范围内创建Timer实例,然后从该范围返回:

1)计时器是否仍然执行?

2)什么时候会被垃圾收集?

我提供以下两种情况:

Timer timer = new Timer(new TimerCallback((state) => { doSomething(); }));
timer.Change((int)TimeSpan.FromSeconds(30), (int)TimeSpan.FromSeconds(30));
return;

并且

Timer timer = new Timer(new TimerCallback((state) => { doSomething(); }));
timer.Change((int)TimeSpan.FromSeconds(30), Timeout.Infinite);
return;

4 个答案:

答案 0 :(得分:6)

TimerCallback引用了方法DoSomething(),因此(在您的示例中)引用了this,但没有其他方式的实时引用,因此应该收集它。 ..eventually

答案 1 :(得分:3)

计时器可能执行也可能不执行,取决于垃圾收集是否在执行时间之前运行。这就是为什么在堆栈以外的地方保留对定时器的引用是好的做法。

请注意,这并不总是有问题的;例如,只要它们仍在运行,就不会收集线程。

答案 2 :(得分:3)

这是一个快速测试:

class Program
{
    static void Main(string[] args)
    {
        Something something = new Something();
        Foo(something);
        Console.ReadKey(true);
        GC.Collect();
        Console.ReadKey(true);
    }

    private static void Foo(Something something)
    {
        Timer timer = new Timer(new TimerCallback(something.DoIt),null,0,5);
        return;
    }
}

public class Something
{
    public void DoIt(object state)
    {
        Console.WriteLine("foo{0}", DateTime.Now.Ticks);
    }
}

这基本上就是编译器将其搞砸的(示例中的Lambda表达式)。当你运行它时,你会注意到只要你没有按下第一个键,它就会继续把东西放到控制台上。一旦你敲了一把钥匙,然后GC就会启动,它就会停止。 Timer仍然引用了Something,但没有任何引用Timer,所以它已经消失了。

答案 3 :(得分:0)

如果您正在讨论System.Threading.Timer,它会实现IDisposable,因此您应该保留对它的引用,以便在您不再使用它时可以调用Dispose。我不知道您的特定问题的答案,但您可以通过运行多次迭代并强制GC.Collect()查看Timer是否继续触发来在控制台应用程序中进行调查。我的猜测是它最终将被收集并停止触发,除非在内部创建了一些静态的根参考。

另外,如果你想要一次性的Fire-and-forget计时器,你可以通过创建一个引用Timer的状态对象来实现一个,这样它就可以在计时器事件触发时自行处理。我有一个TimerService类,其WhenElapsed(TimeSpan, Action)方法使用此模式,它非常便于创建超时,而无需将Timer实例作为包含类中的字段进行管理。