从System.Timers.Timer编组调用主线程

时间:2013-08-13 10:54:46

标签: multithreading timer marshalling

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

但是,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

2 个答案:

答案 0 :(得分:2)

您的计时器启用/禁用代码有一个微妙的错误。你有:

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

' Do work

' Re-enable the timer
_Timer.Enabled = True

因此,如果在输入时禁用了计时器,则代码仍会执行。当然,你不应该接到多个电话,但你的条件检查基本没用。

更好的方法是在AutoReset设置为False的情况下初始化您的计时器。这使计时器只勾选一次。然后,在事件处理程序结束时,再次调用Start以重新启动计时器。这样你就不可能获得对处理程序的多个并发调用。

正如文档中所指出的,

System.Timers.Timer具有压缩异常的不幸特性:

  

Timer组件捕获并抑制事件处理程序为Elapsed事件抛出的所有异常。

因此,如果您的事件处理程序抛出异常,您将永远不会知道它。除了不会重新启用计时器。所以你需要写:

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

你也可能想在那里处理异常。否则你永远不会知道它们会发生。

答案 1 :(得分:1)

将来自工作线程的调用编组到特定其他线程并非易事。只有某种线程可以支持这一点。 Winforms或WPF应用程序中的主线程符合条件,它们是特殊的,因为它们具有调度程序循环。 producer-consumer problem的通用解决方案。

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

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

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