|
A simple app demonstrates unexpected behavior of .NET timers and Thread.Sleep(); native multimedia timers come to the rescue. category 'KB', language C#, created 02-Feb-2007, version V1.0, by Luc Pattyn This article also appeared on the CodeProject. |
|
License: The author hereby grants you a worldwide, non-exclusive license to use and redistribute the files and the source code in the article in any way you see fit, provided you keep the copyright notice in place; when code modifications are applied, the notice must reflect that. The author retains copyright to the article, you may not republish or otherwise make available the article, in whole or in part, without the prior written consent of the author. Disclaimer: This work is provided |
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.
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 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.
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 best timing resolution.
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 thick 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.
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:
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 encounter 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.
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:
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.
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:
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.
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.
Similar code, but using a System.Timers.Timer, same results, and
same remark.
Similar code, but this time based on the Win32 functions timeSetEvent()
and timeKillEvent() from WinMM.dll
The prototypes and delegates involved are:
[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.
Timers do not always behave as one would expect:
If better behavior is required for both .NET Framework versions 1.1 and 2.0, one can use:
The test application uses the following basic techniques:
Perceler |
Copyright © 2012, Luc Pattyn |
Last Modified 21-May-2025 |