VB.NET Abort an asynchronous method call after timeout
VB.NET 2010, .NET 4
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.
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.