SetTimer () Traps

I have a windowless timer (without WM_TIMER) that runs the callback function only once when the specified period of time expires. It is implemented as SetTimer()/KillTimer() . The time periods are quite small: 100-300 milliseconds.

Is it cheap enough (I mean performance) to call the SetTimer()/KillTimer() pair for every such short time interval?

What if I have 100 such timers that periodically call SetTimer()/KillTimer() ? How many timer window objects can exist simultaneously in the system?

Here's the question: Use a bunch of such timer objects and rely on a good implementation of Windows timers, or create one Windows timer object that ticks every, say, 30 milliseconds, and subscribes to all user-timed 100-300 millisecond timers.

thanks

+7
source share
3 answers

The problem with timer messages when you try to use them is that they are low priority messages. These are actually fake messages. The timers are associated with the core object of the kernel timer - when the message loop detects this fire, it simply marks the current message flow queue with a flag indicating that the next GetMessage call - WHEN NO OTHER MESSAGES FOR THE PROCESS - should synthesize the WM_TIMER message just in time and return it.

With a potentially large number of timer objects, it is clear that the system will only signal timer messages for all timers the same way, and any system load can completely prevent the generation of WM_TIMER messages for long periods of time.

If you control the message loop, you can use your own list of timer events (along with GetTickCount timestamps when they should happen) and MSGWaitForMultipleObject - wait for messages instead of GetMessage. Use the dwTimeout parameter to provide the smallest interval - henceforth - until the next timer is signaled. Therefore, it will be returned from pending messages each time you have a timer for processing.

And / Or you can use the expected timers - either in the GUI thread with MSGWaitForMultipleObjects, or just on the workflow to access the lower level of time.
+2
source

The biggest SetTimer() trap is that it is actually a USER object (even though it is not listed in the MSDN USER Object List ), therefore, it falls under the restriction of Windows USER objects - by default, a maximum of 10,000 objects per process , maximum 65535 objects per session (all running processes).

This is easy to verify with a simple test - just call SetTimer() (parameters do not care, both window and window windows act the same way), and the number of USER objects has increased in the task manager.

Also see ReactOS ntuser.h source and this article . Both of them claim that TYPE_TIMER is one of the types of USER descriptors.

So be careful - creating multiple timers can lead to the exhaustion of system resources and the failure of the process or even the entire system.

+2
source

Here are the details that I feel that you are actually asking this question after that:

SetTimer () first scans the list of non-nuclear timers (a doubly linked list) to see if a timer identifier already exists. If the timer exists, it will simply reset. If not, an HMAllocObject is called and creates space for the structure. Then the timer structure will be filled and attached to the list header.

This will be a common invoice for creating each of your 100 timers. This is exactly what the procedure does, with the exception of checking the min and max dwElapsed parameters.

As the timer expires, the timer list is scanned (approximately) for the duration of the shortest timer duration observed during the last scan of the timer list. (In fact, what happens is that the kernel timer is set to the duration of the smallest user timer, and this kernel timer wakes up a thread that checks the expiration of the user timer and wakes up the corresponding threads, setting a flag in their message queue status.)

For each timer in the list, the current delta between the last (in ms) list of the timer was scanned and the current time (in ms) decreases with each timer in the list. When one of them (<= 0 remains), it is marked as β€œready” in its own structure, and a pointer to the stream information is read from the timer structure and used to wake up the corresponding stream by setting the stream flag QS_TIMER. Then it increments your CurrentTimersReady message queue. This all timer expires. Messages not sent.

When your main message channel calls GetMessage (), when there are no other messages, GetMessage () checks the QS_TIMER in your wake-up bits, and if set, generates a WM_TIMER message by scanning the full timer of the list user for the smallest timer in the list marked by READY and associated with your stream id. Then it decreases your stream CurrentTimersReady count, and if 0, clears the bit by timer. The next call to GetMessage () will cause all this to happen until all timers are exhausted.

One shot timers are saved. When they expire, they are marked as WAITING. The next call to SetTimer () with the same timer id will simply update and activate the original again. Both one shot and periodic timers reset themselves and only die with KillTimer or when your thread or window is destroyed.

The implementation of Windows is very simple, and I think it would be trivial if you write a more efficient implementation.

0
source

All Articles