views:

65

answers:

1

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.

+2  A: 

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.

Hans Passant
Thanks for the response. Hmmm, that is a problem. I read about the timer in the Threading namespace a while ago. I think I decided to go with the System.Timers.Timer because it seemed more straightforward to use and because I never have more than a few threads active in my application at any given time. My timer interval is 500ms. Given that, do you think that problems with multiple Work() threads stepping on each other would likely happen. Regardless, your point is taken. I'll try to retool things using the Threading.Timer.
BASnappl
One more thing though... Is there some way to wrap everything in some sort of fail-safe timeout that would force the timer to immediately stop its work, "brush itself off", and start again.
BASnappl
Not sure I got the point across. There's no 'brush off' if the timer can only fire once. But my real point was to take a good look at what SerialPort can do and re-consider the idea of polling. Polling sucks, it burns needless cycles and threads and its hard to keep state. SerialPort.DataReceived avoids polling but not state. Blocking read calls don't require state, but burns a Thread. I know, tough when somebody tells you that you ought to rewrite the code from scratch.
Hans Passant
I've gotcha. I'm alright rewriting it once I have a clear idea of what to do. I really appreciate the advice.
BASnappl