Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread.Sleep or Thread.Timer hanging system?

I have been looking around for over a week now, and I haven't been able to find anyone else having a similar problem to what I'm seeing here.

I'm working with an OLD application running on Windows XP and developed in Visual Studio 2003. All of a sudden about 3 weeks ago, the application becomes unresponsive and the operator has to do the Windows three-finger-salute (CTRL-ALT-DEL) to bring up the task manager, kill the application process (which is shown as Not Responding) and restart the application.

I managed to have it happen once in the debugger, and when I paused the application it was waiting the system to return from attempting to set the System.Windows.Forms.Timer.Interval property.

Here's the object and where it appeared to hang, this isn't how it's written in the source code

internal static System.Windows.Forms.Timer timerMtimeOut;

// This is in an initialization method.
timerMtimeOut = new System.Windows.Forms.Timer();   
timerMtimeOut.Tick += new System.EventHandler(timerMtimeOut_Tick);

// this is how it's value is set.
timerMtimeOut.Interval = 1 * msec;  // <-- This is where it was in the debugger
timerMtimeOut.Enabled = true;


private static void timerMtimeOut_Tick(object sender, System.EventArgs e)
{           
    mTimeOut +=timerMtimeOut.Interval/msec;
}

The application essentially becomes unresponsive and has to be shut down using the Task Manager and restarted.

It's been working fine for years and then this just started happening about 2-3 weeks ago.

Has anyone else seen this behavior?

like image 444
fmarcelino Avatar asked Nov 10 '22 03:11

fmarcelino


1 Answers

The most likely cause is that you are using the timer in a way it was not built for. The first line of code gives a hint:

internal static System.Windows.Forms.Timer timerMtimeOut;

There is no good reason to declare a System.Windows.Forms.Timer static, because this specific timer is designed to be linked to a specific Window (Form instance), and will cause the tick events to be fired in that form's main thread. It does so by sending messages into the threads message queue when the timer hits a tick. The message is then processed in the forms thread (WndProc) together with all other messages such as mouse movement and keyboard input.

If you instantiate multiple windows which access the timer, then problems will turn up sooner or later.

When the OS says that a process is not responding, it detects this by looking at how messages in the main threads message queue are being processed. If they are not being processed, then either the main thread is running a lengthy routine, or has got into some infinite loop. Calling Thread.Sleep() (as stated in your questions title) from the main thread will cause message processing to pause (and will prevent the ticks to be processed during the sleep).

Lengthy procedures should be run in a separate thread in order to keep an application responsive. You probably would like to show a progress bar and also give the user the opportunity to abort the process. This is hard to do if you run the lengthy process in the main thread. There is a dirty workaround by calling Application.DoEvents() somewhere in the loop, which will process the message queue on the fly. But you should be extremely careful to disable UI buttons when using this workaround, or else your user might start the process multiple times (recursively) or change some other state on which your process depends.

A little off topic, but the name of the variable suggests you are trying to count the number of intervals during a lengthy process? Note that if that lengthy process is running in the Form's main thread, the timer's tick events will queue up in the message loop and will only be executed after the lengthy process has finished its work and message processing is resumed. The approach you mention in the comments with a DateTime field is allot simpler, safer and better than using a timer.

When you need a timer that will execute code immediately when the tick is hit, you could use a System.Threading.Timer instead of the System.Windows.Forms.Timer. If you need to control it from multiple threads, use a System.Timers.Timer instead (that one is thread safe). Note that these timers will execute their event's in different threads. Also note that you should never manipulate controls or form's from other threads than the forms main thread. An easy way to do so is built into the framework with the Control.Invoke() method, which in turn will use the message loop to queue the operation.

like image 143
Louis Somers Avatar answered Nov 14 '22 23:11

Louis Somers