When doing system-related programming, you are sometimes in need of getting notified of system events, e.g. when:
- another process has quit
- you want your application get notified it should quit
- the terminal server is ready
In this article I’m presenting a handy C++ class that solves a common requirement: using the system thread pool in order to get notified once a handle becomes signaled, asynchronously and thread-safe.
The task of watching a handle to become signaled is performed in a different thread of execution, because you don’t want to block the main flow of the program. A quick and dummy approach is creating a thread that waits for a specific handle and then quits. But then you realise you have spun up some threads that do nothing else than occupying system resources and sit around waiting. Also you probably had to create yet another thread class just for this very purpose. And deep inside you feel that there must be a better and de facto standard way saving you from reinventing the wheel once again.
Indeed, since Windows XP/Windows Server 2003 there is a system thread pool that was invented exactly for this purpose (and others), right at your hands via the RegisterWaitForSingleObject and its clean-up counter-part UnregisterWait[Ex]; with the advantage that:
- it saves you from writing yet another thread
- it is much more light-weight and resource-friendly by utilising a thread pool
- you can leverage an existing asynchronous notification system
Now, that sounds simple and easy, but given the whole bunch of possibilities the API is offering, quite the opposite is true. On one hand it’s the way how to use the API to achieve what you want, on the other hand you are not exempt from the caveats of thread-safe coding when it comes to installing and cleaning up the event listener. Even as a seasoned and accurate programmer I had to read the documentation multiple times and investigate thoroughly how other people were using it.
There are enough blogs and mentions about those API calls out there on the net. The unique thing to my post is where the handle watcher comes into play: you only need to pass it a handle and a callback and make sure the callback function is thread-safe and its implementation non-blocking. As a usual bonus when encapsulating dedicated problems your code becomes spaghetti-free and worry-free.
Note: RegisterWaitForSingleObject and the system thread pool open up some powerful possibilities, e.g. periodic notifications. Here we are concentrating only on listening to a handle’s signaled state once, and that’s the only purpose of the handle watcher class.
Now, the core of the class is quite straightforward:
The way it works is as follows:
watch_this takes the handle (correct: taking ownership) to get registered in the thread pool and a callback function (which is also called a work item on MSDN). The callback is registered to be called exactly once and not everytime the handle would become signaled.
Now that being said you can figure what the private method signaled is up to: it is registered as an internal signal handler. When it gets called it invokes the callback you provided to watch_this, then resets the handle and unregisters it from the thread pool, unconditionally, meaning it doesn’t wait for any queued callbacks. This might be logical but it is an important detail when calling UnregisterWaitEx - the callback (signaled itself or the callback you passed to watch_this must be non-blocking).
signaled also resets the callback as soon as it has been executed - a measure of freeing resources early, which makes sense to me because the handle watcher usually lives during the whole application uptime. Because the handle watcher gets notified from any thread out of the thread pool one has to ensure that destroying possibly bound parameters is thread-safe.
cleanup is used when you stop watching the handle upon program exit. It unregisters the handle from the thread pool, but waits until any already queued callbacks have been called. This is another reason why the callback itself must be non-blocking, which in turn means it must not be called from your provided callback! cleanup is a no-op if the watcher has been notified - signaled has already performed the necessary cleanup.
In case the handle didn’t become signaled and the callback wasn’t called, cleanup releases the handle and returns it to the caller. The handle can then be used further e.g. for quitting the associated application. In case the handle became signaled and the callback has been invoked the returned handle is simply null.
Of course all of the operations are performed in a thread-safe way. The only thing you need to ensure is that your callback function, the work item, is thread-safe and non-blocking as well, otherwise the notifying thread cannot return to the pool or cleanup might dead-lock. Best practice is to asynchronously notify the thread that installed the watch, which gives you the simplicity of single-threaded processing (see the excellent comment on this topic).
A Usage Example
Let’s take a look at how you would ensure an application you started from your program keeps running. Let’s assume you have the following WTL window:
All set? Attached you will find the whole implementation of the handle watcher. Have fun and keep the copyright!