We continue our exercise of emulating the REG_
flag by making the whole thing a coroutine, assuming you’re willing to take the anachronism to an extreme by using C++20 features in code intended to run on Windows XP.
auto RegNotifyChangeKeyValueAsync( HKEY hkey, BOOL bWatchSubtree, DWORD dwNotifyFilter, HANDLE hEvent) { struct awaiter { HKEY m_hkey; BOOL m_bWatchSubtree; DWORD m_dwNotifyFilter; HANDLE m_hEvent; LONG m_result; std::experimental::coroutine_handle<> m_handle; bool await_ready() const noexcept { return false; } bool await_suspend(std::experimental::coroutine_handle<> handle) { m_handle = handle; if (!QueueUserWorkItem( Callback, this, WT_EXECUTEINPERSISTENTTHREAD)) { m_result = static_cast<LONG>(GetLastError()); return false; } return true; } LONG await_ready() const noexcept { return m_result; } DWORD CALLBACK Callback(void* param) { auto self = reinterpret_cast<awaiter*>(param); self->m_result = RegNotifyChangeKeyValueArgs( self->m_hkey, self->m_bWatchSubtree, self->m_dwNotifyFilter, self->m_hEvent, TRUE); self->m_handle(); return 0; } }; return awaiter(hkey, bWatchSubtree, dwNotifyFilter, hEvent); }
The catch here is that the coroutine continues on the persistent thread, and you’re not supposed to run long operations on the persistent thread, so the caller should probably resume_background
to get onto a non-persistent thread pool thread.
We can’t do the work ourselves of resuming on a non-persistent thread pool thread, say, by doing another Queue
, because if the second call fails, we are stuck on the persistent thread. If we are willing to bump the minimum system requirements to Windows Vista, we could preallocate the work items and remove the possibility of getting stuck halfway through.
So let’s go all the way with this absurd exercise, next time.
I couldn't help but ask "how long does the persisted pool thread persist"?
And the docs say:
"The callback function is queued to a thread that never terminates. It does not guarantee that the same thread is used each time. This flag should be used only for short tasks or it could affect other timer operations...Note that currently no worker thread is truly persistent..."
It says the thread doesn't terminate but then there's a wink that says...
There are two `await_ready` in the code. Is it a mistake? I guess the second one should be `await_resume`.
Also
Callback
should bestatic
We can’t… [do] another QueueUserWorkItem, because if [that] fails, we are stuck on the persistent thread.
Couldn’t
resume_background
fail? And ifQueueUserWorkItem
fails, couldn’t we simply handle the error and leave?Maybe link
resume_background
to its Docs page (since it’s not part of standard C++ and the reader may not know what it is)?