如何管理在函数范围内声明的Timers的清理?

时间:2014-07-18 10:25:25

标签: c# .net timer garbage-collection system.timers.timer

在以下代码中,Timer在函数内声明,它还订阅了Elapsed事件:

    void StartTimer()
    {
        System.Timers.Timer timer = new System.Timers.Timer(1000);
        timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
        timer.AutoReset = false;
        timer.Start();
    }

一旦函数完成,对Timer的引用就会丢失(我猜)。

Elapsed事件是否会在对象被销毁时自动取消注册,如果没有,可以在Elapsed事件本身中取消注册事件:

    void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        var timer = (System.Timers.Timer)sender;
        timer.Elapsed -= timer_Elapsed;
    }

这种方法是否仍允许正确清理对象,或者由于这个原因,定时器是否永远不会在函数的本地范围内声明?

2 个答案:

答案 0 :(得分:13)

这里的规则很复杂,你无法看到CLR里面发生了什么。其中维护了一个活动计时器列表,System.Timers.Timer在该列表中有一个引用,使其保持活动状态并防止它被垃圾收集。在您的情况下是必要的,因为您的StartTimer()方法中的局部变量不足以使其保持活动状态。

当AutoReset = false时,CLR在计时器中从列表中删除计时器。 only 引用左侧是Elapsed事件处理程序中的 sender 参数。

如果未使用 sender 显式重新启用计时器,从而将其放回CLR队列,则Timer对象不会有任何引用。只要GC运行,它就会被垃圾收集。

取消订阅Elapsed事件处理程序对此没有影响。这是另一个很难看到的细节,您的活动订阅添加了对 this 的引用。换句话说,Timer对象实际上使您的外部对象保持活动状态。这当然是一件好事,你不希望你的对象被垃圾收集,而计时器仍然可以调用你的Elapsed事件处理程序。如果你希望该对象的生命周期没有被计时器扩展,那么你将不得不做更多的工作。现在有必要显式取消订阅事件处理程序停止计时器。这要求您保留对Timer对象的引用。

还要记住,如果你的类本身实现了IDisposable,那么它也应该处理Timer。这是必要的,因为您通常不希望Elapsed事件处理程序在已处置的对象上运行,这往往会触发ObjectDisposedExceptions。再次保持将Timer对象引用存储在类的字段中的原因。请注意隐藏在地板垫下的非常讨厌的线程竞争错误,Elapsed事件仍然可以在或之后运行,而则调用计时器的Dispose()方法。需要联锁以防止一年一月或一个月的蓝月亮使您的程序崩溃。与允许代码在工作线程上运行并访问共享状态时必须采取的正常预防措施没有区别。

总结一下,如果你没有进一步使用Timer,那么将它放在Elapsed事件处理程序中是合乎逻辑的事情。实际上并不是必需的,不活动的计时器不会消耗系统资源,但.NET程序员通常对跳过它非常不舒服。再次进行线程竞赛是可能的,您可以配置已经处理过的计时器,但这不会造成麻烦。

答案 1 :(得分:2)

只需处理计时器:

void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    var timer = (System.Timers.Timer)sender;
    timer.Dispose(); //here
}

没有必要解开事件,因为按照惯例,处置一直是完全处置。

不过,你在那里写的所有东西都相当于:

await Task.Delay(1000);