Date: | December 23, 2004 / year-entry #432 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20041223-00/?p=36913 |
Comments: | 12 |
Summary: | The CreateTimerQueueTimer function allows you to create one-shot timers by passing the WT_EXECUTEONLYONCE flag. The documentation says that you need to call the DeleteTimerQueueTimer function when you no longer need the timer. Why do you need to clean up one-shot timers? To answer this, I would like to introduce you to one of my favorite... |
The Why do you need to clean up one-shot timers? To answer this, I would like to introduce you to one of my favorite rhetorical questions when trying to puzzle out API design: "What would the world be like if this were true?" Imagine what the world would be like if you didn't need to clean up one-shot timers.
Well, for one thing, it means that the behavior of the
function would be confusing. The caller of the
the But far, far worse is that if one-shot timers were self-deleting, it would be impossible to use them correctly. Suppose you have an object that creates a one-shot timer, and you want to clean it up in your destructor if it hasn't fired yet. If one-shot timers were self-deleting, then it would be impossible to write this object. class Sample { HANDLE m_hTimer; Sample() : m_hTimer(NULL) { CreateTimerQueueTimer(&m_hTimer, ...); } ~Sample() { ... what to write here? ... } };
You might say, "Well, I'll have my callback null out the
Except that's a race condition. Sample::Callback(void *context) { /// RACE WINDOW HERE ((Sample*)context)->m_hTimer = NULL; ... } If the callback is pre-empted during the race window and the object is destructed, and one-shot timers were self-deleting, then the object would attempt to use an invalid handle. This race window is uncloseable since the race happens even before you get a chance to execute a single line of code. So be glad that you have to delete handles to one-shot timers. |
Comments (12)
Comments are closed. |
While on the subjuct of freeing handles, I wish Microsoft made it more clear about whether or not a particular resource needs to be freed. Take for example the API CreateIcon (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/resources/icons/iconreference/iconfunctions/createicon.asp), no where on this page does it say you need to free the handle. If you read other pages in the MSDN it says you must call DestroyIcon, but the help page for CreateIcon doesn’t mention this at all. I just wish Microsoft were more explicit.
I wonder why do I have to use DeleteTimerQueueTimer() instead of CloseHandle()?
(same with FindClose() etc.)
Why is the type a HANDLE if I can’t do HANDLE operations with it?
CloseHandle works only for handles to kernel objects. It’s too bad that such a generic word (HANDLE) was used for such a specific scope.
anawn:
Maybe because it comes from a different subsystem?
As far as I know, and Raymond would be best suited to answer this, CloseHandle only works on objects that have an underlying kernel object associated with them (files, sections, threads, mutexes etc) since CloseHandle is just a wrapper around ZwCloseHandle.
Innit just so irritating when you click submit only to find someone has already answered the exact same question =)
And the race window is uncloseable ^_^
Think yourself lucky. It’s even more annoying when you hit submit and find that someone else has posted a *contradictory* answer! :)
"You might say, ‘Well, I’ll have my callback null out the m_hTimer variable. That way, the destructor will know that the timer has fired.’"
At that point, you might as well ask the person what they gain by setting a handle to null over calling DeleteTimerQueueTimer…
Heh. I own these APIs these days.
There are other problems. Like, when *do* you delete the timer? Do you do it from your callback? Then your main code has nothing to synchronize on, and you have no idea when your callback has actually completed execution. This can easily cause you to let your dll unload while your callback routine is in progress, barring some custom assembly to do things like call SetEvent() with the return address pointing back to the threadpool.
So you use a timer queue, so that you have something to synchronize with. And all is right with the world, except that the implementation’s slow.
RegisterWaitForSingleObject()’s another API in the same family; it has no equivalent to the timer queue, so there’s no good time for you to clean up your wait object. QueueUserWorkItem() has no handle at all, so there’s nothing to synchronize with.
The plan is to fix & simplify a lot of this stuff in Longhorn. (And "HANDLE" in the new API is replaced with "pointer to undeclared structure", so that you can some actual typechecking and don’t try to CloseHandle() on it.)
12/25/2004 11:24 AM Rob Earhart
> And "HANDLE" in the new API is replaced
> with "pointer to undeclared structure"
Please try to persuade your colleagues who own other APIs to do the same. The MSDN library used to say to use various specific kinds of handles instead of a single generic HANDLE type. I still think that was right, and the move to delete typechecking among all the varieties of handles was a step backwards.
12/25/2004 5:49 PM mirobin
> "You might say, ‘Well, I’ll have my callback
> null out the m_hTimer variable. That way,
> the destructor will know that the timer has
> fired.’"
>
> At that point, you might as well ask the
> person what they gain by setting a handle to
> null over calling DeleteTimerQueueTimer…
By setting a handle to null, the program can later inspect the value of the handle later and see that it’s null. By calling DeleteTimerQueueTimer and omitting changing the handle, the program can later try to guess and hope.
The non-GUI way of scheduling code to run on a thread.