【问题标题】:VB.NET Abort an asynchronous method call after timeoutVB.NET 在超时后中止异步方法调用
【发布时间】:2011-04-22 00:47:37
【问题描述】:

VB.NET 2010,.NET 4

大家好,

我有一个 System.Timers.Timer 对象,它对其经过的事件做了一些工作:

Private Sub MasterTimer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles MasterTimer.Elapsed
    MasterTimer.Enabled = False
    '...work...
    MasterTimer.Enabled = True
End Sub

我的问题是它所做的工作有时会卡住。部分工作是串行通信,因此它可能会在等待某物的响应时卡住。我已经稍微修改了我的串行通信代码,希望能解决这个问题。然而,这个计时器基本上是生产控制应用程序的心跳,如果它因任何原因停止是非常糟糕的。我在想设置一个故障安全超时可能会很好,这样,如果“工作”花费的时间太长,计时器可以重新启用自己并重试。我在想这样的事情:

将工作移入子例程并创建委托:

Private Delegate Sub WorkDelegate()
Private Sub Work()
   '...work...
End Sub

通过调用委托来调用工作,然后在 IAsyncResult 上使用 WaitOne(timeout) 来指定超时:

Private Sub MasterTimer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles MasterTimer.Elapsed
    MasterTimer.Enabled = False
    Dim workDel as New WorkDelegate(AddressOf Work)
    Dim result as IAsyncResult = workDel.BeginInvoke
    result.AsyncWaitHandle.WaitOne(CInt(MasterTimer.Interval))
    MasterTimer.Enabled = True
End Sub

但是,我的问题是:如果 Work() 真的卡在某个地方,这会导致问题吗?因为它会重新进入一个已经运行的子程序?如果 Work() 在超时后没有完成,有没有办法中止它?换句话说,如果在 WaitOne 之后 result.IsCompleted 为 False,就停止 Work() 的执行?

我不是很了解这些东西,所以,任何 cmets 将不胜感激。也许有一种我不知道的完全不同的方法来解决这个问题?

提前非常感谢!

我想补充一点:

虽然我打算按照 Hans 的建议进行一些重写,但我已经安排了一天的测试来尝试找出这个错误的根源。到目前为止,它仅在运行已编译的应用程序时发生。我今天(和昨天)花了一些时间尝试在调试模式下运行时重现冻结,以便我可以添加一些断点并弄清楚发生了什么。到目前为止,该程序还没有在调试模式下冻结。我只是想知道调试环境是否有任何不同可以解释这一点。可能只是我“幸运”,但我运行它的时间可能是程序在运行可执行文件时冻结的平均时间的三倍......再次,我很无知,但有什么调试环境的独特之处可以解释这一点吗?如果卡住我会再更新的。

【问题讨论】:

    标签: vb.net asynchronous timeout timer waithandle


    【解决方案1】:

    这在代码的第一行就出错了。 System.Timers.Timer 类非常讨厌,绝对不能保证调用 Stop() 会阻止另一个调用。计时器使用 ThreadPool.QueueUserWorkItem 进行 Elapsed 事件处理程序调用。如果线程池很忙,这可能最终会排队几个调用,等待从 TP 调度程序获得继续运行以开始运行。停止计时器并不会阻止那些等待的线程运行。如果不使用锁,这些线程将严重相互踩踏,并扰乱您的通信状态。

    一个安全的方法是 System.Threading.Timer ,它的周期为零,所以你只会得到一个回调。使用其 Change() 方法为计时器充电。

    调用委托的 BeginInvoke() 方法然后阻止它完成是没有意义的。只需调用 Invoke()。使您免于烧毁另一个线程。

    是的,如果“工作”方法永远不会返回,那么您就有问题了。一个无法解决的问题。

    如果您避免使用轮询来查看串行端口上是否有任何可用的东西,那么很多这种痛苦可能会消失。让它告诉你有一些值得去做的事情。只要接收缓冲区中至少有一个字节,它就会在线程池线程上引发 DataReceived 事件。使用其 WriteTimeout 属性也是避免在通信协议或设备出现问题时卡住的好方法。专用一个线程并进行阻塞读取调用也很有效。

    【讨论】:

    • 感谢您的回复。嗯,这是个问题。不久前,我在 Threading 命名空间中阅读了有关计时器的信息。我想我决定使用 System.Timers.Timer 是因为它看起来更易于使用,而且在任何给定时间,我的应用程序中的活动线程都不会超过几个。我的计时器间隔是 500 毫秒。鉴于此,您是否认为可能会发生多个 Work() 线程相互踩踏的问题。无论如何,你的观点被采纳了。我会尝试使用 Threading.Timer 重新调整东西。
    • 还有一件事......有没有办法将所有内容包装在某种故障安全超时中,这将迫使计时器立即停止工作,“刷掉自己”,然后重新开始.
    • 不确定我是否明白了这一点。如果计时器只能触发一次,则没有“刷掉”。但我真正的目的是好好看看 SerialPort 可以做什么,并重新考虑轮询的想法。轮询很糟糕,它会消耗不必要的循环和线程,并且很难保持状态。 SerialPort.DataReceived 避免轮询但不是状态。阻塞读取调用不需要状态,但会烧掉一个线程。我知道,当有人告诉你应该从头开始重写代码时,这很难。
    • 我明白了。一旦我清楚地知道该怎么做,我就可以重写它。我非常感谢您的建议。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-15
    • 2018-05-21
    • 2023-04-06
    • 2015-10-11
    • 2020-05-30
    • 1970-01-01
    相关资源
    最近更新 更多