Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / C#
Article

Timer surprises, and how to avoid them

Rate me:
Please Sign up or sign in to vote.
4.82/5 (72 votes)
2 Feb 2007CPOL6 min read 199.2K   3.6K   98   50
A simple app demonstrates unexpected behavior of .NET timers and Thread.Sleep(); native multimedia timers come to the rescue.

Test Application screenshot

Introduction

The .NET Framework offers different timer classes; each of them can deliver periodic events, and the period is user selectable. Since their period is expressed as a number of milliseconds, one could expect such events could reach a frequency up to 1000 Hz. But this is not at all true, the maximum frequency is far less, and it depends on your specific system.

The TimerTest application provides a simple means to test the behavior of the different .NET timers on your specific system. It also includes some operations based on native code (performance counters and multimedia timers), showing more accurate time measurements and higher timer frequencies can be achieved.

The problem

Windows, first and for all, has a "system timer" that is the primary means for keeping track of time, for switching tasks, and for invoking single-shot or periodic timer events upon the user's request. Historically, the system timer has been based on simple hardware, and a system timer frequency of 50 to 60 Hz seemed appropriate.

Server applications probably do not mind at all; typical desktop applications, when implemented correctly, can also work fine with such a clock; some applications, both multimedia and others, however, may need a finer notion of time.

Nonetheless, the .NET timer classes seem to rely on the good old system timer resolution, and hence are unable to provide short intervals such as 1 or 5 milliseconds, or high frequencies such as 500 or 1000 Hz.

The test program

The program is fairly simple. There is only one form, it holds a listbox showing all output, and a collection of buttons, each calling one of the timer tests.

Get System Timer Period

This calls the Win32 function GetSystemTimeAdjustment() and returns the period of the system timer. I have tried this on a couple of PCs and got values of 10 and 15 - 16 milliseconds; I also recall this function returned 55 to 60 msec on an old Pentium II machine running Windows 98 (which also could run .NET!). So the message is twofold: the number may vary, and it is well above 1 msec, so this is not the way to achieve the best timing resolution.

DatTime.Now.Ticks

This is a short loop calling DateTime.Now.Ticks and comparing it to the previous result. Most of the time, the difference is zero, suggesting an infinitely fast machine. And when the difference is not zero, it equals a large number, such as 100000 or 156250, which corresponds to 10 msec or 15.625 msec since it counts "ticks" (a tick always is 100 nsec). So the surprise here is that the DateTime.Now struct, while holding a millisecond field, only gets updated 60 to 100 times per second, so formatting DateTime.Now.ToString("HH:mm:ss.fff") to obtain milliseconds will show at most 60 to 100 different combinations for the fractional digits.

Accurate Measurements

We now use a Win32 PerformanceCounter to get more precise time measurements. A performance counter is based on different hardware: a separate 64-bit counter is counting a fixed frequency, and its value can be read at all times. The overall approach expressed in pseudo-code:

C#
long freq=GetPerformanceCounterFrequency();
long start=GetPerformanceCounterValue();
...code to be timed
long stop=GetPerformanceCounterValue();
double secondsElapsed=((double)(stop-start))/freq;

The test times a sequence of half-second delays (Thread.Sleep(500)) and the results prove that a much higher resolution is obtained. Most systems seem to have a PerformanceCounterFrequency of either 3,579,545 Hz or 10,000,000 Hz; the former is a popular frequency for real-time clock devices, the latter corresponds to the tick frequency. I have never encountered a frequency below 1 MHz, so I would conclude this method of measuring elapsed time is adequate down to a few tens of microseconds.

The test also contains an "empty test", where the code to be timed is empty, resulting in around 10 microseconds of measurement overhead.

Thread.Sleep()

This test performs a number of Thread.Sleep() calls, with a period of 25, 10, 5, or 1 milliseconds; and it uses the above performance counter to measure the elapsed time in each case. The surprise now is the sleep time is not what we asked for, it is longer, and how much longer heavily depends on the system:

  • on one system, I observed all delays corresponded to the requested time rounded up to the next multiple of the system timer period (so 25 became 30 msec), and 10, 5, and 1 all resulted in about 10 milliseconds.
  • on another system, I observed the requested 25, 10, 5, and 1 msec resulted in an average of 25.5, 10.8, 5.9, and 2.0 msec, which is not too bad.

Unfortunately, I see no major reason for such a different behavior, and I can not predict what it will be without running a test application like the one described here.

Form.Timer Interval

This test will launch a System.Form.Timer and wait for it to finish a number of events. The timer period is user selectable through the TextBox, the value must be in the range [1, 1000] msec. The number of events measured is such that the total test time is approximately 5 seconds. Again, in pseudo-code:

C#
timer.Interval=int.Parse(TextBox.Text);
int maxCount=5000/timer.Interval;
timer.Start();

private void timerEventHandler(...) {
    count++;
    if (count>=maxCount) {
        ... stop the test
    }
}

The result for all systems seems to be that the requested timer period always gets rounded up to the next system timer period multiple, so the shortest achievable interval equals 10 to 16 milliseconds. Please note that the event handler is extremely short, except for its last invocation when the timer gets stopped and the results get reported.

Thread.Timer Interval

Similar code, but using a System.Thread.Timer, same results. Remark: the timer's event handler runs on a different thread, so the log method needed the InvokeRequired/Invoke pattern to keep the listbox happy.

Timers.Timer Interval

Similar code, but using a System.Timers.Timer, same results, and same remark.

Multimedia Timer Interval

Similar code, but this time based on the Win32 functions timeSetEvent() and timeKillEvent() from WinMM.dll.

The prototypes and delegates involved are:

C#
[DllImport("WinMM.dll", SetLastError=true)]
private static extern uint timeSetEvent(int msDelay, int msResolution,
        TimerEventHandler handler, ref int userCtx, int eventType);

[DllImport("WinMM.dll", SetLastError=true)]
static extern uint timeKillEvent(uint timerEventId);

public delegate void TimerEventHandler(uint id, uint msg, 
       ref int userCtx,    int rsv1, int rsv2);

With this timer, the results are excellent: asking a 1 millisecond interval really generates a 1 millisecond interval. So this is the timer to use when small intervals are important. Be careful though, the threading and Invoke remark applies here too. Of course, if the event handler were to consume more time (e.g., by updating the user interface through Invoke and delegates), the results could easily become much worse.

Conclusions, points of interest

Timers do not always behave as one would expect:

  • the standard timer classes typically generate too large an interval; they will not reach their theoretical maximum frequency of 1000 Hz.
  • the Thread.Sleep() method may or may not largely exceed the delay you asked for.
  • on some systems, Thread.Sleep(1) will not even come close.

If better behavior is required for both .NET Framework versions 1.1 and 2.0, one can use:

  • A Win32 performance counter for measuring elapsed times in the millisecond and submillisecond range.
  • A Win32 multimedia timer for generating timer events up to 1000 Hz (make sure to keep the handler short!).

The test application uses the following basic techniques:

  • InvokeRequired and Invoke to have timer event handlers interact with the form's controls.
  • PInvoke to call native Win32 functions.
  • Since the timeSetEvent() function needs a callback, the application shows how to have Windows call a user-supplied C# method.

History

  • LP TimerTest# 1.0 (first release).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Belgium Belgium
I am an engineer with a background in electronics, software and mathematics.

I develop technical software, both for embedded systems and for desktop equipment. This includes operating systems, communication software, local networks, image processing, machine control, automation, etc.

I have been using all kinds of microcontrollers and microprocessors (Intel 4004/8080/8051/80386/Pentium, Motorola 680x/680x0/ColdFire/PowerPC, Microchip PIC, Altera NIOS, and many more), lots of programming languages (all relevant assemblers, Fortran, Basic, C, Java, C#, and many more), and different operating systems (both proprietary and commercial).

For desktop applications and general development tools I have been using both UNIX systems and Mac/MacOS for many years, but I have switched to x86-based PCs with Windows, Visual Studio and the .NET Framework several years ago.

I specialize in:
- cross-platform development (making software that runs on diverse hardware/OS combinations)
- instruction set simulation
- improving software performance, i.e. making sure the software runs the job at hand in as short a time as possible on the given hardware. This entails algorithm selection, implementation design, accurate measurements, code optimisation, and sometimes implementing virtual machines, applying SIMD technology (such as MMX/SSE), and more.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Akram El Assas29-Feb-12 5:18
Akram El Assas29-Feb-12 5:18 
GeneralRe: My vote of 5 Pin
Luc Pattyn29-Feb-12 5:38
sitebuilderLuc Pattyn29-Feb-12 5:38 
GeneralMy vote of 5 Pin
Erik Rude19-Dec-11 6:22
Erik Rude19-Dec-11 6:22 
GeneralMy vote of 5 Pin
hmasd21-May-11 8:48
hmasd21-May-11 8:48 
GeneralMy vote of 5 Pin
JOAT-MON8-Jan-11 12:44
JOAT-MON8-Jan-11 12:44 
GeneralGood work Pin
Ekvilibrium5-Apr-10 9:34
Ekvilibrium5-Apr-10 9:34 
GeneralRe: Good work Pin
Luc Pattyn5-Apr-10 12:08
sitebuilderLuc Pattyn5-Apr-10 12:08 
Generallook at System::Threading::timers Pin
robinson oliva17-Jun-09 6:41
robinson oliva17-Jun-09 6:41 
AnswerRe: look at System::Threading::timers Pin
Luc Pattyn17-Jun-09 7:37
sitebuilderLuc Pattyn17-Jun-09 7:37 
GeneralRe: look at System::Threading::timers Pin
robinson oliva17-Jun-09 8:47
robinson oliva17-Jun-09 8:47 
AnswerRe: look at System::Threading::timers Pin
Luc Pattyn17-Jun-09 8:56
sitebuilderLuc Pattyn17-Jun-09 8:56 
GeneralRe: look at System::Threading::timers Pin
robinson oliva17-Jun-09 9:04
robinson oliva17-Jun-09 9:04 
GeneralRe: look at System::Threading::timers Pin
general.failure13-Jul-09 1:40
general.failure13-Jul-09 1:40 
AnswerRe: look at System::Threading::timers Pin
Luc Pattyn13-Jul-09 1:49
sitebuilderLuc Pattyn13-Jul-09 1:49 
GeneralRe: look at System::Threading::timers Pin
general.failure13-Jul-09 8:09
general.failure13-Jul-09 8:09 
GeneralMy vote of 2 Pin
Ali Tarhini16-Jan-09 22:22
Ali Tarhini16-Jan-09 22:22 
GeneralRe: My vote of 2 PinPopular
sBoff29-Jan-09 12:09
sBoff29-Jan-09 12:09 
GeneralRe: My vote of 2 Pin
Xmen Real 23-Mar-09 17:57
professional Xmen Real 23-Mar-09 17:57 
GeneralThread.Sleep halts the program longer than i request Pin
Ali_quaidian7-Dec-08 7:22
Ali_quaidian7-Dec-08 7:22 
AnswerRe: Thread.Sleep halts the program longer than i request Pin
Luc Pattyn7-Dec-08 8:04
sitebuilderLuc Pattyn7-Dec-08 8:04 
GeneralThis is the best article never read .... really Pin
Giacomo Veneri6-Dec-08 8:57
Giacomo Veneri6-Dec-08 8:57 
GeneralRe: This is the best article never read .... really Pin
Luc Pattyn6-Dec-08 9:05
sitebuilderLuc Pattyn6-Dec-08 9:05 
GeneralTypo Pin
Pete O'Hanlon17-Oct-07 0:47
subeditorPete O'Hanlon17-Oct-07 0:47 
AnswerRe: Typo Pin
Luc Pattyn17-Oct-07 1:16
sitebuilderLuc Pattyn17-Oct-07 1:16 
GeneralRe: Typo Pin
Pete O'Hanlon17-Oct-07 2:11
subeditorPete O'Hanlon17-Oct-07 2:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.