The PulseEvent
function
releases one thread (or all threads, if manual-reset)
which is/are waiting for the pulsed event, then returns the
event to the unset state.
If no threads happen to be waiting, then the event goes to
the unset state without anything happening.
And there’s the flaw.
How do you know whether the thread that you think is waiting on the event really is? Surely you can’t use something like
SignalSemaphore(hOtherSemaphore); WaitForSingleObject(hEvent, INFINITE);
because there is a race between the signal and the wait. The thread that the semaphore is alerting might complete all its work and pulse the event before you get around to waiting for it.
You can try using
the SignalObjectAndWait
function,
which combines the signal and wait into a single operation.
But even then, you can’t be sure that the thread is waiting
for the event at the moment of the pulse.
While the thread is sitting waiting for the event,
a device driver or part of the kernel itself
might ask to borrow the thread to do some
processing (by means of a “kernel-mode APC”).
During that time, the thread is not in the
wait state.
(It’s being used by the device driver.)
If the PulseEvent
happens while the thread
is being “borrowed”, then it will not
be woken from the wait, because the PulseEvent
function wakes only threads that were waiting
at the time the PulseEvent
occurs.
Not only are you (as a user-mode program) unable to prevent kernel mode from doing this to your thread, you cannot even detect that it has occurred.
(One place where you are likely to see this sort of thing happening is if you have the debugger attached to the process, since the debugger does things like suspend and resume threads, which result in kernel APCs.)
As a result, the PulseEvent
function is useless
and should be avoided. It continues to exist solely for backwards
compatibility.
Sidebar: This whole business with kernel APCs also means that you cannot predict which thread will be woken when you signal a semaphore, an auto-reset event, or some other synchronization object that releases a single thread when signalled. If a thread is “borrowed” to service a kernel APC, then when it is returned to the wait list, it “goes back to the end of the line”. Consequently, the order of objects waiting for a kernel object is unpredictable and cannot be relied upon.
0 comments