Why bother with RegisterWaitForSingleObject when you have MsgWaitForMultipleObjects?

Commenter kokorozashi wonders why you should bother with RegisterWaitForSingleObject when you have MsgWaitForMultipleObjects already. If you want to pump messages and wait for a kernel object, then you can change all calls to PeekMessage, GetMessage, and WaitMessage to replacement functions that use MsgWaitForMultipleObjects. Isn't that enough? Why waste an entire thread just to wait for that object?

If you're so clever that you can modify every call to PeekMessage, GetMessage, and WaitMessage, then more power to you. But in order to do this, you'll have to restrict the functions you call, because all sorts of functions contain their own message loops. Do you call MessageBox? Or DialogBox? Those functions contain a modal loop. (After all, they don't return until the user dismisses the dialog box; somebody has to be pumping messages because you're not.) Indeed, you can't even call DefWindowProc since that function goes into a modal loop if the user, say, grabs the caption bar and drags the window around: That drag loop happens inside DefWindowProc.

If your thread has any sort of visible UI, this sort of extreme control of all message loops is unreasonable. You have no practical choice but to have the wait happen on some other thread and either respond to the signalled object on that thread or post a notification back to the UI thread to do the work.

The advantage of RegisterWaitForSingleObject over creating your own thread for waiting is that the thread pool functions will combine multiple registered waits together on a single thread (by the power of WaitForMultipleObjects), so instead of costing a whole thread, it costs something closer to (but not exactly) 1/64 of a thread.

Comments (2)
  1. Rob Shearman says:

    In addition to the point Raymond makes some Win32 code may not even have a message loop, such as services or library code. In such a case, having an easy way to make a wait operation happen asynchronously is beneficial. Even in the case where you do have a message loop, the operation that you perform when the object is signalled may take a long time to complete and so you wouldn’t want to do it on a UI thread anyway.

  2. Arlie says:

    There’s another set of good reasons to use RegisterWaitForSingleObject (or just plain WaitForSingleObject or WaitForMultipleObjects in your own worker thread).

    Every time a thread calls any WaitFor* function, the kernel needs to translate handles to object pointers, and needs to link up the wait blocks for the current thread in the headers of the waitable objects (in the portion of the objects’ header that is owned by the dispatcher).  This is work, and it is work that is proportional to the number of handles, and the number of times you call any WaitFor* method.

    GUI threads typically have a high rate of message processing, and the speed which those messages are processed has a strong effect on the quality of the user’s experience.  If you are managing an array of wait handles, and you use MsgWaitForMultipleObjects in order to multiplex between wait handles and USER32/GUI messages, then you’re adding a fair amount of processing to every user/kernel transition.

    If you are managing a large number of handles, and those handles become signalled relatively infrequently (for example, notifications from the Plug-and-Play manager, or a directory notification event for your configuration files, etc.), then you should use RegisterWaitForSingleObject.  The thread (from the thread pool) which does the waiting will get to sleep for a long time, and so all of its private data will get pushed out of L1 and L2 cache, leaving you more room in L1 and L2 for your "real" work.  Then, in your callback function, you can use PostMessage to your main thread to forward the message.  This gives you the simplicity of single-threaded processing (you don’t have to worry about locks nearly as much), but also gives you decent performance, and also side-steps the problem Raymond described, with modal message pumps that are beyond your control.

    If you have wait handles that are signaled at a very high rate, such as asynchronous I/O for networking or disk I/O, then you probably want to be doing this on a worker thread anyway, rather than your GUI thread.  You should still use PostMessage to send messages to your GUI thread.  (SendMessage is evil because it blocks, but sometimes that’s the least-evil choice.)

Comments are closed.

Skip to main content