VB.NET 超时后中止异步方法调用
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() 吗?
我不太了解这些东西,因此,任何评论将不胜感激。也许有一种我不知道的完全不同的方法来解决这个问题?
预先非常感谢!
我想补充一点:
虽然我打算按照汉斯的建议进行一些重写,但我已经安排了一天的测试来尝试隔离此错误的根源。到目前为止,它仅在运行已编译的应用程序时发生。我今天(以及昨天的一些)尝试在调试模式下运行时重现冻结,以便也许我可以添加一些断点并弄清楚发生了什么。到目前为止,程序还没有冻结在调试模式下。我只是想知道调试环境是否有什么不同可以解释这一点。可能只是我很“幸运”,但我运行它的时间可能是平均时间长度的三倍,之后程序在运行可执行文件时冻结了......再说一遍,我很无知,但是有什么吗?调试环境的独特性可以解释这一点吗?如果冻结的话我会再次更新。
VB.NET 2010, .NET 4
Hello all,
I have a System.Timers.Timer object that does some work on its elapsed event:
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
My problem is that the work that it's doing sometimes gets stuck. Part of the work is serial communication so it might be getting stuck waiting for a response from something. I've already modified my serial communication code a bit to hopefully solve the problem. However, this timer is basically the heartbeat of a production control application and it is very bad if it were to stop for any reason. I was thinking that it might be nice to put in a fail-safe timeout so that, if the "work" is taking too long, the timer could re-enable itself and try again. I was thinking of something like this:
Move the work into a subroutine and create a delegate:
Private Delegate Sub WorkDelegate()
Private Sub Work()
'...work...
End Sub
Call the work by invoking the delegate and then use WaitOne(timeout) on the IAsyncResult to specify a 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
But, my question is: Would this cause a problem if Work() really was stuck somewhere? In that it would re-enter a subroutine that is already running? Is there a way to abort Work() if it hasn't finished after the timeout? In other words, just cease execution of Work() if result.IsCompleted Is False after WaitOne?
I don't really understand this stuff very well so, any comments would be greatly appreciated. Perhaps there's an entirely different way to approach this that I don't know about?
Thanks a lot in advance!
I would like to add something:
Although I intend to do some rewrites as per Hans' suggestions, I had already scheduled a day of testing to try to isolate the source of this error. So far, it has only occurred when running the compiled application. I have spent today (and some yesterday) trying to reproduce the freeze while running in debug mode so that maybe I could add some breakpoints and figure out what's going on. So far, the program hasn't frozen in debug mode. I'm just wondering if there's anything different about the debug environment that might explain this. It might just be that I'm being "lucky", but I've run it probably three times the average time length after which the program froze when running the executable... Again, I'm pretty ignorant, but is there anything unique about the debug environment that could explain this? I'll update again if it freezes.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这在代码的第一行就出错了。 System.Timers.Timer 类非常棘手,绝对不能保证调用 Stop() 会阻止另一个调用。计时器使用 ThreadPool.QueueUserWorkItem 来调用 Elapsed 事件处理程序。如果线程池很忙,这可能会导致多个调用排队,等待 TP 调度程序开始运行。停止计时器并不会阻止那些等待线程的运行。如果不使用锁,这些线程将严重相互干扰并扰乱您的通信状态。
安全的方法是 System.Threading.Timer,其周期为零,因此您只会得到一个回调。使用 Change() 方法为计时器重新充电。
调用委托的 BeginInvoke() 方法然后阻止其完成是没有意义的。只需调用 Invoke() 即可。避免你烧毁另一个线程。
是的,如果“Work”方法永远不会返回,那么就有问题了。一个无解的。
如果您避免使用轮询来查看串行端口上是否有任何可用内容,那么很多这种痛苦可能会消失。让它告诉你有一些值得发生的事情。只要接收缓冲区中至少有一个字节,它就会在线程池线程上引发 DataReceived 事件。使用其 WriteTimeout 属性也是避免在通信协议或设备出现问题时陷入困境的绝佳方法。专用一个线程并进行阻塞 Read 调用也效果很好。
This goes wrong on the very first line of the code. The System.Timers.Timer class is pretty icky, there's absolutely no guarantee that call Stop() will prevent another call. The timer uses ThreadPool.QueueUserWorkItem to make the Elapsed event handler call. If the threadpool is busy this may end up queuing several calls, waiting to get the go-ahead from the TP scheduler to start running. Stopping the timer doesn't prevent those waiting threads from running. Without using a lock, those threads will step on each other badly and mess up your communication state.
A safe one is System.Threading.Timer with a period of zero so you'll get only one callback. Recharge the timer with its Change() method.
Calling a delegate's BeginInvoke() method and then blocking on it completing doesn't make sense. Just call Invoke(). Saves you from burning up another thread.
Yes, if the 'Work' method never returns then you have a problem. An unsolvable one.
A lot of this misery could disappear if you avoid using polling to see if there's anything available on the serial port. Let it tell you that there's something worthwhile going on. It raises the DataReceived event, on a threadpool thread, whenever there's at least one byte in the receive buffer. Using its WriteTimeout property is also an excellent way to avoid getting stuck when something is amiss with the communication protocol or the device. Dedicating one Thread and making blocking Read calls works well too.