There is an obvious difference between System.Windows.Forms.Timer and System.Timers[System.Threading].Timer – the former is handled with Win32’s WM_TIMER message and thus it needs a message loop which usually means you need a Windows application.
But there’s also a much less obvious difference – and this difference is the reason to write this entry.
Suppose you set your Windows.Forms timer to 50 miliseconds and then a single Tick lasts a little bit longer:
// execute the Tick every 50 miliseconds
private void timer1_Tick( object sender, EventArgs e )
{
this.richTextBox1.Text += DateTime.Now.ToString() + Environment.NewLine;
// but a single Tick lasts longer
Thread.Sleep( 1000 );
}
What happens then? You see, a WM_TIMER message is constrained by the system so that is never occurs too often. It means that the message queue will not be flooded with extra messages and you are safe. In the above scenario, the RichTextBox would be updated once for a second, even if the timer is set to launch every 50 miliseconds.
But the case is completely different with the two latter timers! The operating system launches a new thread with every tick so that you are not safe from having multiple ticks running in the same time.
static void Main( string[] args )
{
System.Threading.Timer t = new Timer( timer_callback, null, 0, 50 );
while ( true ) { }
}
static void timer_callback( object state )
{
Console.WriteLine( DateTime.Now.ToString() );
Thread.Sleep( 1000 );
}
Run the above code snippet to see that the console is actually flooded with messages arriving independently much ofter than you’d expect. This time it’s important then to manually handle the flooding:
static void Main( string[] args )
{
System.Threading.Timer t = new Timer( timer_callback, null, 0, 50 );
while ( true ) { }
}
static bool worksNow;
static void timer_callback( object state )
{
// exit if in the middle of action
if ( worksNow ) return;
try
{
// disable temporarily
worksNow = true;
Console.WriteLine( DateTime.Now.ToString() );
Thread.Sleep( 1000 );
}
finally
{
// make sure that the handler is always reenables
worksNow = false;
}
}
There’s still a small issue with such approach. This time, when the timer interval is only a fraction smaller than the time of the action, every second tick of the timer will exit because of the value true of the guarding worksNow variable but then there will be a long period of inactivity before next timer’s tick. You’d rather like the timer to resume the work immediately after the handler completes.
static void Main( string[] args )
{
System.Timers.Timer t = new System.Timers.Timer( 50 );
t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);
t.Start();
while ( true ) { }
}
static void t_Elapsed( object sender, System.Timers.ElapsedEventArgs e )
{
System.Timers.Timer Timer =
(System.Timers.Timer)sender;
try
{
// disable temporarily
Timer.Stop();
Console.WriteLine( DateTime.Now.ToString() );
Thread.Sleep( 1000 );
}
finally
{
// make sure that the handler is always reenables
Timer.Start();
}
}
The only subtle issue here is that, as you’ve probably noticed, there does not seem to be an easy way to stop/start System.Threading.Timer, so I had to switch to System.Timers.Timer.
Complete list of different timers with their profiles is available on the MSDN Magazine.
No comments:
Post a Comment