Killing a window timer prevents the WM_TIMER message from being generated for that timer, but it doesn’t retroactively remove ones that were already generated


Calling Kill­Timer to cancel a window timer prevents WM_TIMER messages from being generated for that timer, even if one is overdue. In other words, give this sequence of operations:

SetTimer(hwnd, IDT_MYTIMER, 1000, NULL);
Sleep(2000);
KillTimer(hwnd, IDT_MYTIMER);

no WM_TIMER message is ever generated. Even though a timer became due during the Sleep, no timer message was generated during the sleep because timer messages are generated on demand, and nobody demanded one. Killing the timer then removes the ability to demand a timer message, and the result is that no message ever appears.

In general, this means that once you kill a timer, you will not receive any WM_TIMER messages for that timer.

Unless you demanded one while the timer was active and didn't process it.

Let's try a variation:

SetTimer(hwnd, IDT_MYTIMER, 1000, NULL);
Sleep(2000);
if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, 0)) {
 DispatchMessage(&msg);
}
KillTimer(hwnd, IDT_MYTIMER);

In this case, the Peek­Message function looks for a WM_TIMER message in the queue, and if none is found, it asks for one to be generated on the fly if a timer is due. It so happens that one is due (IDT_MY­TIMER), so the Peek­Message causes a WM_TIMER to be generated and placed in the queue. But it doesn't remain in this state for long, because the message is removed from the queue by the Peek­Message function.

Okay, now let's make things weird:

SetTimer(hwnd, IDT_MYTIMER, 1000, NULL);
Sleep(2000);
if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE)) {
 // oh hey there is an overdue timer, how about that
}
KillTimer(hwnd, IDT_MYTIMER);

This time, we passed the PM_NO­REMOVE flag. The window manager goes through the same process as before, first looking for a WM_TIMER message in the queue, and then failing to find one, generates one on the fly since the IDT_MY­TIMER timer is overdue. But the PM_NO­REMOVE flag makes things weird because it says, "Thanks for generating that message for me. But don't remove it from the queue. Leave it there. I'll deal with it later."

You might do this if you want to stop processing if a timer elapses, but you don't want to handle the timer immediately because you are in some sensitive state at the point you realize that you need to stop processing. Instead, you want to return back out to the main message loop and let it deal with the timer.

BOOL DoWorkUntilTheNextTimer()
{
 BOOL fFinished = FALSE;
 MSG msg;
 PrepareToDoWork();
 while (!PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE)) {
  if (AnyWorkLeft()) DoSomeWork(); 
  else { fFinished = TRUE; break; }
 }
 CleanUpAfterDoingWork();
 return fFinished;
}

And then you might call it like this:

void DoWorkForUpToOneSecond()
{
 SetTimer(hwnd, IDT_MYTIMER, 1000, NULL);
 DoWorkUntilTheNextTimer();
 KillTimer(hwnd, IDT_MYTIMER);
}

The Kill­Timer will prevent any new timer messages from being generated for IDT_MY­TIMER, but it does not go back in time and retroactively un-generate the timer message that was generated when Do­Work­Until­The­Next­Timer asked to see if there were any timer messages.

You are now in the strange situation where a subsequent call to Peek­Message or Get­Message will retrieve a timer message for a timer that is no longer active!

This is captured in the MSDN documentation with the simple sentence, "The Kill­Timer function does not remove WM_TIMER messages already posted to the message queue."

Comments (7)
  1. Joshua says:

    Let me guess, Raymond had to debug a shell extension messing this up about a queue length ago.

  2. OldFart says:

    This recent string of message-queue-related posts made me wonder, when is a message filter (or PeekMessage for that matter) ever a proper solution to a problem?  It has been years since I've attempted to do anything tricky in a message pump, but my best recollection is that the unintended consequences of any such attempt way overwhelmed any benefits.

  3. rs says:

    I suspect 0 should be PM_REMOVE in the first PeekMessage example (0 is PM_NOREMOVE).

  4. EduardoS says:

    Get­Message blocks the thread, in the case of WM_TIMER, how is the process of wakeing up the thread and posting the message?

    [The same way Get­Message wakes up for other external events, like a new posted message or an inbound sent message. -Raymond]
  5. Jim says:

    "But it doesn't remain in this state for long, because the message is removed from the queue by the Peek­Message function." – Pretty sure you meant "… by the DispatchMessage function" (as opposed to rs's suggestion)

    [Nope. PeekMessage and GetMessage remove messages from the queue. DispatchMessage dispatches messages after they have already been removed from the queue. It is legal to remove messages from the queue without dispatching them. -Raymond]
  6. Jim says:

    Oops, sorry! Thanks for the reply (and the post of course, which is interesting as always).

  7. Azarien says:

    As far as I understand, DispatchMessage just calls WndProc with a given message, and does little else (that "little else" includes some calling convention magic that is not needed in normal circumstances).

Comments are closed.

Skip to main content