0

我有一个棘手的问题(至少对我来说)。我正在开发一个用 VB.net 编写的 Windows 服务。我正在使用 System.Timers.Timer 类定期调用委托方法以查看是否有任何工作要做。处理的时间并不重要,我试图通过在调用方法后立即禁用 Timer 并在最后重新启动它来防止重新进入工作方法。

但是,Timer 类 Elapsed 事件发生在不同的线程上。在网上搜索,大多数人都在使用实现 ISynchronize 接口的 Windows 窗体来编组对原始线程的调用。理想情况下,我不想使用 Windows 窗体来实现这一点。有没有一种简单的方法可以将调用重定向回原始线程?

或者是否有一个我可以继承的框架类来做到这一点?或者最坏的情况是 ISynchronize 的简单实现?

Imports System.Timers
Imports System.IO
Imports System.Data


Public Class Application
    Implements IDisposable


Private WithEvents _Timer As Timer



Private Sub SleepTimerCallback(sender As Object, e As ElapsedEventArgs) Handles _Timer.Elapsed

    ' TODO need to find a way to bring this method back on the main thread.

    ' Temporarily disable the timer elapsed event so that we don't have event re-entrance.
    If Me._Timer.Enabled = True Then Me._Timer.Enabled = False

    ' Do Work.

    ' Re-enable the timer elapsed event.
    _Timer.Enabled = True

End Sub

End Class
4

2 回答 2

2

Your timer enabling / disabling code has a subtle bug. You have:

If Me._Timer.Enabled = True Then Me._Timer.Enabled = False

' Do work

' Re-enable the timer
_Timer.Enabled = True

So if the timer is disabled on entry, your code still executes. Granted, you shouldn't get multiple calls, but your conditional check there is essentially useless.

A better way to do it is to initialize your timer with AutoReset set to False. That makes the timer tick only once. Then, at the end of your event handler, call Start again to restart the timer. That way you can't possibly get multiple concurrent calls to the handler.

System.Timers.Timer has the unfortunate property of squashing exceptions, as pointed out in the documentation:

the Timer component catches and suppresses all exceptions thrown by event handlers for the Elapsed event.

So if your event handler throws an exception you will never know it. Except that the timer won't be re-enabled. So you need to write:

Private Sub SleepTimerCallback(sender As Object, e As ElapsedEventArgs) Handles _Timer.Elapsed
    Try
        ' TODO need to find a way to bring this method back on the main thread.
    Finally    
        ' Re-enable the timer elapsed event.
        _Timer.Start()
    End
End Sub

And you'll probably want to handle exceptions in there, too. Otherwise you'll never know that they occur.

于 2013-08-13T12:11:19.167 回答
1

将来自工作线程的调用编组到特定的其他线程并非易事。只有某些类型的线程可以支持这一点。Winforms 或 WPF 应用程序中的主线程符合条件,它们很特别,因为它们有一个调度程序循环。生产者-消费者问题的通用解决方案。

他们需要这样做是有充分理由的,用户界面基本上是线程不安全的。您只能从创建它的线程更新 UI。因此,始终可以让代码在该特定线程上运行非常重要。

服务中完全缺少此基础架构。它甚至没有“主线程”的概念。它不需要它,没有 UI。因此,您不需要编组调用,只需让您的 Elapsed 事件处理程序完成这项工作。

请注意,您必须注意一些细节以确保 Elapsed 事件处理程序安全。您应该将计时器的 AutoReset 属性设置为 False 以确保在它仍然忙碌时不会再次调用事件处理程序。这很少有好的结局。您自己已经这样做了,但使用 AutoReset 会更好。而且您应该始终使用 Try/Catch 来捕获异常。Timer 类有一个讨厌的习惯,即在没有诊断的情况下吞下异常,您的服务将停止运行,您将不知道为什么。在 Catch 子句中记录异常,以便您知道它为什么停止。

于 2013-08-13T12:22:04.353 回答