For some time now I've been having this recurrent nightmare (read - bug in my application). For some reason, a certain Timer continues to send "Elapsed" events after I stopped it, even though in the event itself the timer "admits" to having been disabled! Check this out:
//Timer is created in Class' Constructor. Class is not static.
public PDAAccess ()
{
ConnectionTimeoutChecker = new System.Timers.Timer(1000);
ConnectionTimeoutChecker.Elapsed += new System.Timers.ElapsedEventHandler(ConnectionTimeoutChecker_Elapsed);
}
void ConnectionTimeoutChecker_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{ //_DeviceConTimeout eventually reaches A LOT MORE than 10.
if (_DeviceConTimeout > 10)
{
ConnectionTimeoutChecker.Stop(); //This is invoked but the timer DOES NOT STOP.
if (OnDeviceSyncTimeout != null) OnDeviceSyncTimeout(this, null); //This gets fired all the time.
}
_DeviceConTimeout++; //This keeps increasing and increasing.
//Worth mentioning: sender = Timer, sender.Enabled = false (!) so then why is this executing?
}
As for where I start it: I start it in a single place, I p开发者_如何学Cut a breakpoint there and it doesn't execute more than once. And before you ask: no multiple threads are involved. I do work with threads in this application, but: the timer is not created in a thread, neither is the Class.
Yet the .Stop(); is executed 100 times and the timer still WON'T stop.
I'm completely at a loss here. This behavior is so strange to me, it gives me that embarrassed feeling that I might have been missing something super-obvious. Sometimes writing such a post helps me identify the problem before I hit the submit button (we all know the "Explaining to 3rd party" effect). But it didn't hit me yet so I'm gonna hit the button and... see what you see ::- D.
Shot in the dark: Perhaps the call to OnDeviceSyncTimeout()
somehow indirectly causes the timer to be reactivated? I know you said it is started only in a single place, but I’m not really sure how you can be so certain about that.
Well, that doesn't make much sense with the code snippets you posted. But extraordinary problems like this require extraordinary explanations.
The System.Timers.Timer class is a horrid timer. The condition you see, getting the Elapsed event called while the timer is stopped, is inevitable in most any use for it. The problem is that it raises the Elapsed event by using ThreadPool.QueueUserWorkItem(). That thread is subject to the vagaries of the thread pool scheduler and the Windows thread scheduler. QUWI does not guarantee that the thread will run right away. Instead, the thread goes in a queue in the "ready to run" state. The TP scheduler removes it from that queue only when it deems the time right to run a thread. Which for one means that it will be stuck for a while if it already has as many threads running as your machine has CPU cores. Only when threads don't complete will it allow more threads to run. That takes a while, these scheduling decisions only run twice a second.
The Windows thread scheduler plays a role as well. When the machine is loaded, having many threads ready to run then it can take a while before the Elapsed thread gets a turn. What is especially nasty about this problem is that such a race is heavily dependent on what else is going on in your program or on the machine. Runs fine on your dev machine, malfunctions unpredictably in production when the Elapsed event finally manages to run, even though the timer got stopped a long time ago.
Also note that this can build up. You are particularly vulnerable to that because you stop the timer in the Elapsed event. When the TP threads just don't manage to get scheduled, the timer just keeps calling QUWI, addding more TP threads, without your code able to stop the timer.
Well, guesses, but it does explain the problem. Either the synchronous timer in your UI library or System.Thread.Timer should fix it. Do try to use the single-shot version of the latter. A time-out is a fixed time, it shouldn't have to be counted.
精彩评论