如何管理在函数作用域中声明的计时器的清理?

本文关键字:声明 计时器 作用域 函数 何管理 管理 | 更新日期: 2023-09-27 18:13:41

在下面的代码中,在一个函数中声明了一个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;
    }

这种方法是否仍然允许对对象进行适当的清理,或者出于这个原因不应该在函数的局部作用域中声明计时器?

如何管理在函数作用域中声明的计时器的清理?

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

使用AutoReset = false, CLR在计时器滴答时从列表中删除计时器。唯一的引用是Elapsed事件处理程序中的sender参数。

如果你没有通过使用发送者显式地重新启用计时器,从而将其放回CLR队列中,那么就没有对timer对象的引用了。当GC运行时,它将被垃圾收集。

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

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

总之,如果你没有进一步使用计时器,那么在经过的事件处理程序中处理它是合乎逻辑的事情。它实际上并不是必需的,不活动的计时器不会消耗系统资源,但是。net程序员通常对跳过它感到非常不舒服。同样,线程竞争也是可能的,您可以释放一个已经被释放的计时器,但这不会造成麻烦。

处理定时器:

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

解除事件的挂钩是不必要的,因为按照约定,处置总是一个完整的处置。

顺便说一句,你在这里写的所有内容都相当于:

await Task.Delay(1000);