We continue our exercise of emulating the REG_
flag, this time for the case where we don’t have a readily-available persistent thread. In that case, we can use one from the thread pool, but it’ll be a bit trickier because we’ll have to wait for the thread pool work item to run in order to get the result.
struct RegNotifyChangeKeyValueAsyncArgs { HKEY hkey; BOOL bWatchSubtree; DWORD dwNotifyFilter; HANDLE hEvent; LONG result; HANDLE hComplete; ~RegNotifyChangeKeyValueAsyncArgs() { if (hComplete) CloseHandle(hComplete); } }; DWORD CALLBACK RegNotifyChangeKeyValueOnPersistentThread( void* param) { auto args = reinterpret_cast< RegNotifyChangeKeyValueAsyncArgs*>(param); args->result = RegNotifyChangeKeyValue( args->hkey, args->bWatchSubtree, args->dwNotifyFilter, args->hEvent, TRUE); SetEvent(args->hComplete); return 0; } // See discussion before using this function. LONG RegNotifyChangeKeyValueAsync( HKEY hkey, BOOL bWatchSubtree, DWORD dwNotifyFilter, HANDLE hEvent) { RegNotifyChangeKeyValueAsyncArgs args = { hkey, bWatchSubtree, dwNotifyFilter, hEvent, ERROR_INVALID_PARAMETER, CreateEvent(nullptr, TRUE, FALSE, nullptr) }; if (!args.hComplete) { return static_cast<LONG>(GetLastError()); } if (!QueueUserWorkItem( RegNotifyChangeKeyValueOnPersistentThread, &args, WT_EXECUTEINPERSISTENTTHREAD)) { return static_cast<LONG>(GetLastError()); } WaitForSingleObject(args->hComplete, INFINITE); return args.result; }
One of the tricks of this exercise is limiting the solution to the features that were available in Windows XP.
Since we need to do the registration from a thread that will remain running indefinitely, we ask the thread pool for help by calling Queue
with the WT_
flag. The callback runs on a persistent thread, and it is there that we call RegNotifyChangeKeyValue
and pass the result back. Meanwhile, the main thread waits for the work item to finish so it can propagate the results to the caller.
This design seems to work, but it has a trap: If the caller of RegNotifyChangeKeyValueAsync
is itself on the thread pool, then you end up with the risk of starving the thread pool and preventing it from doing the thing that would relieve the starvation.
We’ll look at ways of fixing this next time.
Since this operation fundamentally (as viewed from the userland) has thread affinity, I’d probably just spin up my own thread to perform it.
This can get expensive if you have 200 objects that all want to monitor registry keys. Are you going to create 200 threads and keep them alive for the lifetime of their respective objects?
I wanted to say I’d create one thread that sits in a loop handing requests from the objects; I’m just not comfortable using a thread pool thread to do this.