The last SignalObjectAndWait parameter, bAlertable, has been FALSE in previous examples. By using trUE instead, we indicate that the wait is a so-called alertable wait and the thread enters an alertable wait state. The behavior is as follows.
If one or more APCs are queued to the thread (as a QueueUserAPC target thread) before either hObjectToWaitOn (normally an event) is signaled or the time-out expires, then the APCs are executed (there is no guaranteed order) and SignalObjectAndWait returns with a return value of WAIT_IO_COMPLETION.
If an APC is never queued, then SignalObjectAndWait behaves in the normal way; that is, it waits for the object to be signaled or the time-out period to expire.
Alterable wait states will be used again with asynchronous I/O (Chapter 14); the name WAIT_IO_COMPLETION comes from this usage. A thread can also enter an alertable wait state with other alertable wait functions such as WaitForSingleObjectEx, WaitForMultipleObjectsEx, and SleepEx, and these functions will be useful when performing asynchronous I/O.
q_get and q_put (see Program 10-4) can now be modified to perform an orderly shutdown after an APC is performed, even though the APC function does not do anything other than print a message and return. All that is required is to enter an alertable wait state and to test the SignalObjectAndWait return value, as shown by the following modified version of q_get (see QueueObjCancel.c on the Web site).
DWORD q_put (queue_t *q, PVOID msg, DWORD msize, DWORD MaxWait) { BOOL Cancelled = FALSE; if (q_destroyed(q)) return 1; WaitForSingleObject (q->q_guard, INFINITE); while (q_full (q) && !Cancelled) { if (SignalObjectAndWait(q->q_guard, q->q_nf, INFINITE, TRUE) == WAIT_IO_COMPLETION) { Cancelled = TRUE; continue; } WaitForSingleObject (q->q_guard, INFINITE); } /* Put the message in the queue. */ if (!Cancelled) { q_remove (q, msg, msize); /* Signal that queue is not full as we've removed a message. */ PulseEvent (q->q_nf); ReleaseMutex (q->q_guard); } return Cancelled ? WAIT_TIMEOUT : 0; }
The APC routine could be either ShutDownReceiver or ShutDownTransmitter, as the receiver and transmitter threads use both q_get and q_put. If it were necessary for the shutdown functions to know which thread they are executed from, use different APC argument values for the third QueueUserAPC arguments in the code segment preceding