The five timers in C#.
If you want to count time or execute a method repeatedly at regular intervals, timers offer an easy solution. Compared with other solutions, like threading, timers won’t tie up a thread and are relatively accurate. The .NET provides five! different timers. Each of those is to be used differently depending on the situation. The timers which we have in our toolbox are the following:
System.Windows.Forms.Timer
System.Windows.Threading.DispatcherTimer
System.Threading.Timer
System.Timers.Timer
PeriodicTimer
(from .NET6 )
Single-threaded timers
The System.Windows.Forms.Timer
and System.Windows.Threading.DispatcherTimer
are for Windows Forms and WPF respectively and are not to be used outside these environments. They expose Start
and Stop
methods, a Tick
event that behaves like the Elapsed
event in the System.Timers.Timer class, but they differ in how they are implemented. Instead of using a different thread, they use the same thread that created them, in these cases probably the UI thread. That means they are not multithreaded. One thread is responsible both for the UI and the timer and because of that, the event handler for those timers must execute fast or we risk the chance of having the UI unresponsive for some time.
Their precision, is a little less accurate than the multi threaded timers because they can be delayed while other code is executing.
Multi-Threaded timers
The precision of the multi-threaded timers is comparable to the single-threaded timers. That is, they have accuracy in the 10 to 20 milliseconds range. Usually, they are a little more accurate than the single-thread timers, due to the fact that they are executed in a different thread.
The System.Threading.Timer
The System.Threading.Timer, is the simplest timer C# has to offer. It has a constructor and two methods: Change and Dispose/DisposeAsync. Its usage is pretty simple:
using System;
using System.Threading;
Timer timer = new Timer (WriteTime!, null, 3000, 1000);
Console.ReadLine();
timer.Dispose(); // Stop and clean up the timer.
void WriteTime(object data) =>
Console.WriteLine(DateTime.Now.ToString("ss.fff"));
The above code will start displaying seconds and milliseconds, three seconds after starting and each second after that. The timer interval, can be changed later, by calling the Change
method and if we need the timer to execute just one time, then instead of adding a number for the repeat interval in in the constructor, we should pass the Timeout.Infinite
constant as the last parameter.
The System.Timers.Timer
The System.Timers.Timer is actually a wrapper over the System.Threading.Timer that adds additional functionality.
- It has an
Interval
property instead of the Change method. - uses an
Elapsed
event instead of a callback delegate at the constructor. - The timer can start and stop by using the
Enabled
property or itsStart
andStop
methods. - Has an
AutoReset
flag for repeating, that defaults to true. If you want to use it only once this flag must be set to false. - There is a
Close
method that releases the resources used by the timer that is also calling itsDispose
method. - Finally a
SynchronizingObject
Property that gets or sets the object used to marshal event-handler calls that are issued when an interval has elapsed.
Its usage is also very simple:
using System;
var timer = new System.Timers.Timer();
timer.Interval = 1000;
timer.Elapsed += WriteTime!;
timer.Start();
Console.ReadLine();
timer.Stop();
Console.ReadLine();
timer.Elapsed -= WriteTime!;
timer.Elapsed += WriteTimeAgain!;
timer.Start();
Console.ReadLine();
timer.Dispose();
void WriteTime(object sender, EventArgs e) =>
Console.WriteLine(DateTime.Now.ToString("ss.fff"));
void WriteTimeAgain(object sender, EventArgs e) =>
Console.WriteLine(DateTime.Now.ToString("mm.ss.fff"));
For the multi-threaded timers the callback method or Elapsed event can fire on a different thread each time it is called. For this reason we can use the SynchronizingObject property in the System.Timers.Timer to set it to the UI object we need.
Timers and memory leaks
Timers can cause memory leaks if they are not disposed, that’s why they implement the Dispose method. Consider the following:
using System.Timers;
class MemoryLeak
{
Timer timer;
MemoryLeak()
{
timer = new System.Timers.Timer { Interval = 1000 };
timer.Elapsed += WriteTime;
timer.Start();
}
void WriteTime(object sender, EventArgs e) =>
Console.WriteLine(DateTime.Now.ToString("ss.fff"));
}
the MemoryLeak class instance, can never be garbage collected, because the runtime holds references to active timers so that their events can be fired. That means that the runtime will keep alive the timer and by extension the timer will keep the MemoryLeak instance alive because of the timer.Elapsed event handler.
The System.Threading.Timer however is a different case. The runtime doesn’t hold references to the timer in the System.Threading.Timer namespace. Instead references the callback delegate.
That means that our timer can be garbage collected, before it has done the job we need it for. As an example:
static void Main()
{
var timer = new System.Threading.Timer (WriteTime, null, 3000, 1000);
GC.Collect();
System.Threading.Thread.Sleep (20000);
}
void WriteTime(object data) =>
Console.WriteLine(DateTime.Now.ToString("ss.fff"));
This code, if compiled in a release build, won’t execute as we expect. In a debug build, the timer will be kept alive till the end of the scope for debugging purposes, but in a release build, with optimizations enabled the Garbage Collector will collect the timer before it executes the WriteTime method. This can be fixed by informing the runtime that we are actually using the timer and we need it to be disposed only when we are done with it, like this:
using (var timer = new System.Threading.Timer (WriteTime, null, 3000, 1000))
{
GC.Collect();
System.Threading.Thread.Sleep (20000);
}
The PeriodicTimer
The PeriodicTimer introduced in .NET 6, exists to solve the above complications and to help with asynchronous looping. Because of async/await we can have code that doesn’t use the above timers, but repeats time like this:
using System;
using System.Threading.Tasks;
RepeatForEver();
Console.ReadLine();
async void RepeatForEver()
{
while (true)
{
await Task.Delay (1000);
Console.WriteLine(DateTime.Now.ToString("ss.fff"));
}
}
If we call the RepeatForEver method from a UI thread, then this will behave as a single-threaded timer because the await will always return on the same synchronization context. This can be solved by using .ConfigureAwait(false)
, but the real problem here (other that it repeats forever, this is just for the example) is that we will always have a few milliseconds loss, because of the time that takes for the other statements to execute. That means that the Console.WriteLine statement won’t execute every second, but every second plus a few milliseconds. For many repeats, that loss in accuracy adds up and can be a problem.
The solution to the above is the PeriodicTimer class. Consider the following code:
using System;
using System.Threading;
var timer = new PeriodicTimer (TimeSpan.FromSeconds (1));
RepeatForEver();
Console.ReadLine();
timer.Dispose();
async void RepeatForEver()
{
while (await timer.WaitForNextTickAsync())
Console.WriteLine(DateTime.Now.ToString("ss.fff"));
}
This code will still have a few milliseconds difference from a round second, but in each repeat PeriodicTimer will try to compensate for the difference in time. An example output will be like this:
30.220
31.216
32.216
33.219
34.220
35.221
36.222
37.223
38.220
39.216
40.215
41.229
42.214
43.215
44.216
45.219
46.220
47.221
48.217
49.223
As we see the PeriodicTimer will tick around 220 ms, each time adding or removing some ms to keep itself around the 1 second range.
The Dispose method can be used with an active WaitForNextTickAsync to interrupt it, by causing it to return false.
A restriction of the PeriodicTimer, is that only one call to WaitForNextTickAsync can exist at any given moment.
The StopWatch class
The Stopwatch is an accurate measurement of time by using the highest resolution mechanism that the operating system and the hardware can provide. It has a Start
and a Stop
method and an IsRunning
property to return the current state of the stopwatch. The elapsed time, can be found by using its properties: Elapsed
, ElapsedMilliseconds
and ElapsedTicks
.
The IsHighResolution
field returns true if the timer is based on a high-resolution performance counter and false if it is based on the system timer. The Frequency
field returns the number of ticks per second.
Mostly is being used to measure the execution time of some piece of code. Because the Stopwatch class doesn’t provide any events, it must be polled, which will result in loss of accuracy for scheduling tasks. Even if it is wrapped in a class to provide events, it will need a self-correcting algorithm for that loss of accuracy. Because of that, for scheduling tasks, the PeriodicTimer is better, as it already provides all the above. In contrast, for counting accurately time from point A to point B in our code, the StopWatch is preferred.
Interop with winmm.dll
If we want a more accurate timer, from the available ones, that have accuracy about 10 to 20 milliseconds, we can use native interop and call the Windows multimedia timer which has about one millisecond precision. We have to call timeBeginPeriod to inform windows that we need high accuracy and then call timeSetEvent. After that, timeKillEvent will stop the timer and timeEndPeriod will inform windows that high precision isn’t needed anymore.
Beware that this will decrease the performance, because windows will need to give priority to the timer and the timer may not be as accurate as we expect, depending on what other programs are running. For more info on that see: Why are the Multimedia Timer APIs (timeSetEvent) not as accurate as I would expect?
And that’s it about the different timers in C#. I hope you found this info useful and thank you for reading. As always for questions and comments, use the comments section, or contact me directly via the contact form or email, also if you don’t want to miss any of the new articles, you can always subscribe to my newsletter or the RSS feed.