Click here to Skip to main content
Email Password   helpLost your password?

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:

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:

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:

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:

[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:

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:

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Generallook at System::Threading::timers
robinson oliva
7:41 17 Jun '09  
Hi all:
The next lines are MSDN sample modification, timer can't be alive more... than 5 minutes....
Why it could be???
#include "stdafx.h"
using namespace System;
using namespace System::Threading;
ref class StatusChecker
{
private:
static int invokeCount = 0;

public:
StatusChecker( )
{
}

void CheckStatus( Object^ stateInfo )
{
invokeCount++;
Console::WriteLine( DateTime::Now.ToString( "h:mm:ss.fff" ) );
Console::WriteLine(Thread::CurrentThread->ThreadState);
Console::WriteLine(Thread::CurrentThread->ManagedThreadId);
Console::WriteLine("Thread is IsThreadPoolThread ? -- " + Thread::CurrentThread->IsThreadPoolThread.ToString());
Console::WriteLine(invokeCount.ToString());
}

};

int main()
{
StatusChecker^ statusChecker = gcnew StatusChecker();

TimerCallback^ timerDelegate = gcnew TimerCallback( statusChecker, &StatusChecker::CheckStatus );

Console::WriteLine( "Timer.\n", DateTime::Now.ToString( "h:mm:ss.fff" ) );
Timer^ stateTimer = gcnew Timer( timerDelegate,nullptr,1000,250 );

GC::KeepAlive(stateTimer);

Console::ReadKey();
}
GeneralRe: look at System::Threading::timers
Luc Pattyn
8:37 17 Jun '09  
Hi,

your code looks OK; and it has been running fine on my machine, for over 15 minutes now. So I don't know what is happening on your side; you are not typing some key by accident? that would stop the app as it sits waiting for keyboard input (see Console::ReadKey line).

Smile

Luc Pattyn [Forum Guidelines] [My Articles]
DISCLAIMER: this message may have been modified by others; it may no longer reflect what I intended, and may contain bad advice; use at your own risk and with extreme care.

GeneralRe: look at System::Threading::timers
robinson oliva
9:47 17 Jun '09  
Hi Luc:

Thanks for your answer, this code works fine until show 1240th count...then hang...
GeneralRe: look at System::Threading::timers
Luc Pattyn
9:56 17 Jun '09  
Hi,

I stopped it when it ran beyond 4000.
Does yours always stop at exactly the same point?
Do you perhaps have a screen saver kicking in at that point?
Is there anything special about your system?
Is the code you showed all there is to it?
Could you try your EXE on a different machine?

Smile

Luc Pattyn [Forum Guidelines] [My Articles]
DISCLAIMER: this message may have been modified by others; it may no longer reflect what I intended, and may contain bad advice; use at your own risk and with extreme care.

GeneralRe: look at System::Threading::timers
robinson oliva
10:04 17 Jun '09  
That's the full code i'm trying to test...
EXE always hangs at the same point
Probably some system resource is signaling also
i will try in another machine...thanks
GeneralRe: look at System::Threading::timers
general.failure
2:40 13 Jul '09  
I Didn't test your code, but a probable reason is that you used a local variable for your timer. After the timer starts there is no reference anymore to the timer and GarbageCollection can hit anytime, just depending on system load.
To test it, just make your timer static
GeneralRe: look at System::Threading::timers
Luc Pattyn
2:49 13 Jul '09  
Yes, the timer reference needs to be kept alive by putting it elsewhere; a class member is fine, static or instance however is irrelevant.

Smile

Luc Pattyn [Forum Guidelines] [My Articles]
The quality and detail of your question reflects on the effectiveness of the help you are likely to get.
Show formatted code inside PRE tags, and give clear symptoms when describing a problem.

GeneralRe: look at System::Threading::timers
general.failure
9:09 13 Jul '09  
Of course you'r right, making it static was just the most simple way to test if this is indeed the problem. that just requires the var to be declared above the main() with the static keyword. Storing it in a class requires the creation of a class, init it etc.
cheers
GeneralMy vote of 2
Ali Tarhini
23:22 16 Jan '09  
regular information
GeneralRe: My vote of 2
total_havoc
13:09 29 Jan '09  
This post has helped alot of people, its well written, easy to understand, simple examples and to the point. These are the points your vote for an article should be based on, not if you personally know alot on the topic and find it just "regular information".

If you cannot look past the end of your nose and see its value to others who may not be as "smart" as yourself, and generally its value as good collaborative information on the subject, then i do not think you should wast your time writing why you voted the way you did, or even voting at all.

Good article Luc Smile
GeneralRe: My vote of 2
Xmen
18:57 23 Mar '09  
the world is full of trolls and twerp like that one. And they have biggest KEEDA for voting as 1 or 2 though the content is awesome.
Indeed, I call 'em imbecile.


TVMU^P[[IGIOQHG^JSH`A#@`RFJ\c^JPL>;"[,*/|+&WLEZGc`AFXc!L
%^]*IRXD#@GKCQ`R\^SF_WcHbORY87֦ʻ6ϣN8ȤBcRAV\Z^&SU~%CSWQ@#2
W_AD`EPABIKRDFVS)EVLQK)JKQUFK[M`UKs*$GwU#QDXBER@CBN%
R0~53%eYrd8mt^7Z6]iTF+(EWfJ9zaK-i’TV.C\y<pŠjxsg-b$f4ia>
-----------------------------------------------
128 bit encrypted signature, crack if you can

GeneralThread.Sleep halts the program longer than i request
Ali_quaidian
8:22 7 Dec '08  
HI,
I want to develop a 30 rotation per min analog clock for in GDI+. The Thread.Sleep(interval) not works fine and halt the execution little longer. I used Thread.SpinWait(interval) but not get better result. Please guide me what I do ?

Regards,

Quaidian For Once, Quaidian For Ever

GeneralRe: Thread.Sleep halts the program longer than i request
Luc Pattyn
9:04 7 Dec '08  
Hi,

a timer must provide at least the delay you tell it to provide, but there is no guarantee the delay won't be longer. For one, there are many processes and threads all competing for CPU cycles, so when the timer's interval elapses, it is not necessarily handled immediately. Therefore it is often a bad idea to just rely on a timer to create a smooth animation; there would be timing jitter and also all errors would accumulate.

The main idea to solve this is using two things:
- use a timer to get an event, it tells you it is time to do something;
- then use the actual current time to calculate what it is exactly you should do.

Hence for a linear motion, don't just do a Tick handler that basically performs
x = x + a_fixed_delta_x; instead have it perform:
x = x + speed*(current_time - previous_time);
Most often it is wise to keep this x in real numbers (double or float), not integers; and if necessary round it to an integer to map it to screen coordinates.

Hope this helps.

Luc Pattyn [Forum Guidelines] [My Articles]
Fixturized forever. Confused


GeneralThis is the best article never read .... really
Giacomo Veneri
9:57 6 Dec '08  
You solved my problem
GeneralRe: This is the best article never read .... really
Luc Pattyn
10:05 6 Dec '08  
you're welcome.

Smile

Luc Pattyn [Forum Guidelines] [My Articles]
Fixturized forever. Confused


GeneralTypo
Pete O'Hanlon
1:47 17 Oct '07  
Sorry Luc. Just noticed that one of your headings reads DatTime.Now.Ticks.

Deja View - the feeling that you've seen this post before.

GeneralRe: Typo
Luc Pattyn
2:16 17 Oct '07  
Hi Pete, thanks for reporting this; did you click "Broken Article" while rereading? Big Grin

Luc Pattyn [Forum Guidelines] [My Articles]

this months tips:
- use PRE tags to preserve formatting when showing multi-line code snippets
- before you ask a question here, search CodeProject, then Google


GeneralRe: Typo
Pete O'Hanlon
3:11 17 Oct '07  
Luc Pattyn wrote:
did you click "Broken Article" while rereading?

No. I prefer to think of it as a minor abrasion than a break.;)

Deja View - the feeling that you've seen this post before.

GeneralRe: Typo
Luc Pattyn
4:39 17 Oct '07  

Pete O`Hanlon wrote:
I prefer to think of it as a minor abrasion than a break


I'll consider some polish then. Wink

Luc Pattyn [Forum Guidelines] [My Articles]

this months tips:
- use PRE tags to preserve formatting when showing multi-line code snippets
- before you ask a question here, search CodeProject, then Google


GeneralAmazing work.
Pete O'Hanlon
0:02 19 Jul '07  
Well - it's been a long time since I needed to go to this level of accuracy, but this article is wonderful. It's going into my Bookmarks collection - well done.

Please visit http://www.readytogiveup.com/ and do something special today. Deja View - the feeling that you've seen this post before.

GeneralRe: Amazing work.
Luc Pattyn
0:20 19 Jul '07  
Hi Pete,

thank you for your comments.

BTW the readytogiveup URL does not work for me (XP SP2, IE6).

Regards


GeneralRe: Amazing work.
Pete O'Hanlon
6:01 19 Jul '07  
Darn - I guess that Rex has finally taken the site down. I'll have to change my sig.

Please visit http://www.readytogiveup.com/ and do something special today. Deja View - the feeling that you've seen this post before.

QuestionMultimedia Timer in more than 16 thread ^^
Billou_13
0:36 17 Jul '07  
Having problem in average interval, i found your tip and choose to implement multimedia timer solution.
However, it doesn't work for more than 16 multimedia timers declared.
If you want to create another timer after the 16th, it doesn't work (with no error).

Is there a limitation ? Can we solve it ?

Thanks,

Billou_13
AnswerRe: Multimedia Timer in more than 16 thread ^^
Luc Pattyn
1:11 17 Jul '07  
Hi,

thanks for reading my article.

timeSetEvent() returns a positive ID when successful, or a negative error code.
Which means you should check the return value to make sure it succeeded.
the return value being described as an ID typically means the objects get stored in
an array of a fixed (undocumented?) size (other functions return a "handle" which is,
or acts more like, a pointer and does not imply a maximum number of them).

calling timeKillEvent() frees the array entry.

If you need a lot of concurrent timers, this wont solve it. But do you really ?

Say you want to measure elapsed times for 100 different objects; you could try and give each
object a personal timer, but then that would exceed the maximum number of timers.

But you could also install one timer, let it keep track of time (say increment a
variable every millisecond); then have each object read that variable (and remember that
value) at the start and read it again at the end of its period of interest;
the difference would also yield a measurement without the cost of an individual timer
for each object.

Hope this helps.

Smile



GeneralSuggested fix for possible crash !!
black1981
10:02 9 Apr '07  
I did experience a crash using a modified version of the code posted in the article, and I believe it also is present in the existing code.

It seems there is a problem with managed delegates when they are used only by unmanaged code, like in this code from the article:

fastTimer=timeSetEvent(interval, interval, new TimerEventHandler(tickHandler),ref myData, 1);

The problem is that once the Garbage collector sees no managed object referencing the delegate, it gets removed and this causes the later call to it to crash.

The solution is simple - use a delegate variable being held in the class for invocation - as long as the invocation can take place the variable must be available.

This solved the issue for me.


Last Updated 2 Feb 2007 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010