Using the Windows Timer Queue API

Last updated Mar 14, 2003.

The Windows Timer Queue API provides a simple and efficient way to have many high-resolution timers in a single application. The timer queue controls multiple logical timers with a single timer interrupt. This proves to be much less demanding on system resources than having one interrupt per timer--especially when there are many timers.

I've showed in previous articles that .NET timer resolution is limited to about 15 milliseconds, with similar accuracy. Whereas that's sufficient for most applications, there are times when your program really needs better resolution or accuracy. The Timer Queue API looks like the best option for providing such timers under Windows. In this article, we'll look more closely at the API and write a managed wrapper in C# to expose those timers to .NET programs.

Note that, as I've pointed out previously, although the .NET runtime uses a technique very similar to the timer queue, it does not actually call the timer queue API functions. The .NET runtime maintains its own timer queue completely separate from Windows.

The Timer Queue API

The Timer Queue API lets you create and delete timer queues, and create, manipulate, and delete timers in those queues. Applications can also create timers in the default timer queue. You manipulate timer queues with three functions:

In addition to those six functions, the timer queue API depends on a WAITORTIMERCALLBACK, and the flags constants passed to CreateTimerQueueTimer. The code below supplies translations of those, and a managed wrapper for the timer queue API.

The API looks straightforward but, as with many Windows API functions, there are odd special conditions. For example, the TimerQueueTimerFlags enumeration translates the flags constants defined in the timer queue API. The documentation for CreateTimerQueueTimer gives a little bit of information about what the flags mean, but very little guidance about the ramifications of using the different flags. There are possibly serious negative consequences to using some of those flags.

Another example is the completionEvent parameter to DeleteTimerQueueEx and DeleteTimerQueueTimer. The parameter is intended to be the handle of an event that will be signaled when the timer or timer queue has been deleted. However, the behavior depends on how the timer (or the timers in queue) was created. In addition, values of NULL (IntPtr.Zero) and INVALID_HANDLE_VALUE (IntPtr(-1)) cause different behavior.

Although we could create and use timer queue timers through that API, doing so is rather inconvenient. It's much easier from the application standpoint to have an object-oriented wrapper around the API. Creating such a thing is a little bit involved, but it simplifies application code.

An object-oriented wrapper

It's pretty obvious that in order to reflect the timer queue API, we need two classes: a timer queue and a timer. I've named those classes TimerQueue and TimerQueueTimer. Since both classes allocate unmanaged system resources (Windows handles), both implement the IDisposable interface.

The TimerQueue class has a constructor, of course, and a number of overloaded methods that create timers. Since we have to pass a timer queue handle to the CreateTimerQueueTimer API function, it seemed reasonable to have the create method in the TimerQueue class. Otherwise we'd have to pass a TimerQueue reference to the TimerQueueTimer constructor, which I thought would be inconvenient and could lead to some error.

The TimerQueue class has a static property, Default which is simply a wrapper around the default timer queue, allowing you to create timers without having to explicitly create your own timer queue.

CreateTimer creates a periodic timer using the due time and period that you specify, and uses the default flags value. An overload allows you to specify different flags values from the TimerQueueTimerFlags enumeration.

A separate method, CreateOneShot, creates a one-shot timer that will fire once and then stop. Again, an overloaded method allows you to set the flags differently if you like.

Note that I've also created an overloaded Dispose method that lets you pass a completion event parameter.

If you elect to use the creation overloads that let you specify flags, read carefully the information in CreateTimerQueueTimer about what those flags do, and also be mindful of the ramifications when disposing of the timer or timer queue. See the discussion of the completionEvent parameter in the remarks sections of DeleteTimerQueueTimer and DeleteTimerQueueEx. For example, "do not make blocking calls to DeleteTimerQueueEx from within a timer callback." Translated, that means do not try to delete the timer queue inside a timer callback method. Doing so will quite likely result in a crash or a deadlock.

The TimerQueueTimer class is a very thin wrapper around the API. All the class does is keep track of some things (handles, user state, and callback), and shuttle calls to the API. It's a bit disappointing that the API requires me to pass a timer queue handle to the functions that change and delete a timer queue timer. It seems like the timer itself could maintain that information to prevent clients from having to know which queue a particular timer belongs to.

The entire TimerQueueTimer class is less than a hundred lines of code, and most of that is declarations and white space:

Note that the constructor is marked internal. The intention is for the TimerQueue class to have access to this constructor, but no outside callers should have access to it. That way, we know that any timer created is properly initialized with the TimerQueue instance.

You'll notice that the callback function passed to the CreateTimerQueueTimer API function is the TimerCallback method. TimerCallback simply passes the UserState object to the callback method that was specified when the timer was constructed. It's unfortunate having to use this intermediate call-through, but without it the .NET callback would have to be a WaitOrTimerCallback rather than a TimerCallback, and marshaling the UserState would be more difficult, involving mucking about with a GCHandle and such.

Using the new TimerQueueTimer

Using a TimerQueueTimer can be as easy as using a System.Threading.Timer. For example, this bit of code creates a new timer in the default timer queue, writes a message once per second, and exits after 10 seconds:

Using a timer queue timer, that will give you 2,000 ticks in 10 seconds, or one tick every 5 milliseconds. But if you replace the timer with a .NET timer, you'll get one tick every 15 or 16 milliseconds, or about 650 ticks.

There is one other difference whose importance is hard to judge. The .NET timers execute their callbacks on a thread pool thread. The timer queue timers, too, execute callbacks on a thread pool thread. The problem is that they're different thread pools. The .NET runtime apparently manages its own pool of threads that are separate from the Windows thread pool used by the timer queue timers. To see that this is true, use this test:

You'll see that the callback is executing on a pool thread. If you then change the first line so that it creates a timer queue timer:

using (var timer = new TimerQueue.Default.CreateTimer((s) =>

and run the program again, you'll see that the callback is not executed on a .NET thread pool thread.

What this means is that your program could conceivably use more threads than you expect. If you're trying to limit the number concurrent threads by setting the .NET thread pool parameters, those changes won't have any effect on the execution of timer queue callbacks.

Timer queue limitations

I've seen some discussion about how many timers it's possible to have, but I've not seen anything definitive. I have used hundreds of .NET timers in a single application, and have heard from others who have used thousands, all with no ill effects. I've had hundreds of timer queue timers, and the ticks fire exactly on time. Provided, of course, that the system isn't overloaded and there are available thread pool threads.

If you search for information about the number of timers you can have, you'll run across information indicating that the number of timers is limited by the number of thread pool threads. That is incorrect. The number of pool threads will limit the number of ticks that can be processed concurrently, but it has no bearing on the total number of timers that the system will support.

Drawbacks of faster timers

Nothing is free. Using the timer queue timers as I have described will give you much better timer accuracy and resolution, but it comes at a cost. There is a small performance penalty to pay because Windows has to check more often to see if it's time for a timer to fire. With the default timer objects, Windows has to process timer interrupts once every 15 milliseconds, or about 66 times per second. If you set a timer queue timer for 5 milliseconds, Windows has to check 200 times per second. It's unlikely that you'll notice that performance penalty on a fast desktop machine, even if you have a whole bunch of timer queue timers.

However, you may very well notice that a faster default timer frequency results in reduced battery life on your notebook or laptop computer. Modern laptops can put some components into a low-power mode for brief periods and thereby seriously extend battery life. I found it surprising that 15 milliseconds was enough time to save any significant energy, but according to Timers, Timer Resolution, and Development of Efficient Code, setting a timer to even 10 milliseconds can cut battery life by 25%. If notebook battery life is an issue, you probably don't want to be using these timer queue timers except when absolutely necessary.

According to that article, it's not only the period of the timer that affects the interrupt frequency, but also the accuracy. If I read that right, creating a timer queue timer with a period of one second still requires an interrupt every millisecond because the system will try to give you that tick at exactly 1,000 milliseconds. If that's true, then you want to be very careful about when you use these timers.

That said, there's a high likelihood that some other application on the system has already set the minimum timer resolution to 1 ms. If that's true, your application using higher resolution wouldn't be the culprit. Still, it's probably a good idea to limit your use of timer queue timers to those situations when you really need them to give you the best accuracy and resolution.