Last time, we added an extension point that permitted us to respond differently to the completion of the coroutine. We’re going to put that extension point to good use by adding the ability to wait synchronously for the coroutine to complete.
namespace async_helpers::details { template<typename T> struct simple_task_base { ... T get() && { if (!promise->client_await_ready()) { bool completed = false; if (promise->client_await_suspend( &completed, wake_by_address)) { bool ready = true; while (!completed) { WaitOnAddress(&completed, &ready, sizeof(completed), INFINITE); } } } return std::exchange(promise, {})->client_await_resume(); } ... private: ... static void CALLBACK wake_by_address(void* completed) { *reinterpret_cast<bool*>(completed) = true; WakeByAddressSingle(completed); } }; }
To wait synchronously for the coroutine to complete, we first check if the coroutine is already finished. If so, then we’re done, and we can skip the waiting step.
Otherwise, the coroutine is still running, so we register our suspension with a pointer and a callback function. This time, the pointer is a pointer to a variable that we will set when the coroutine is complete, and the callback is a function that sets that variable. In our case, we use WaitÂOnÂAddress
to create a synchronization object out of nothing.
If client_
returns false
, then it means that the coroutine has already completed while we were preparing to suspend, so we should skip the suspend and go straight to the resume step.
Finally, we ask client_
to obtain the completed value and return it. We use std::exchange
to cause the promise_ptr
to be emptied after we get the completed value, thereby consuming it and freeing the coroutine state.
Synchronously waiting for a coroutine is always a risky proposition because the coroutine you’re waiting for might need to use the thread that you are waiting on. This is particularly true if you perform the wait from a single-threaded COM apartment, because the coroutine will probably need to get back to the original thread to continue its COM work, which it can’t do because you’ve blocked the original thread in order to wait for the coroutine.
So don’t do that.
Next time, we’ll look at task interconvertibility.
Hi Raymond,
Thanks for publishing this information. I know there are multiple articles on the Microsoft blog regarding coroutines. I am grateful for this.
I am wondering if you are working in VS2019 c++20 latest? Can you share your code base in aggregate form? Do you have a github site that aggregates the code base that you are using.
I know the cppcoro lib from lewis baker is gcc based and compiles under windows 10. (But it doesn’t handle working in Vs2019 latest build C++20). There is a github fork that i will try shortly that covers some updates for vs2019. We know the microsoft team is working towards supporting C++23 coroutines (with a richer coroutine development).
Is there anyway–a temporary–but current c++20 MSVC compiler version of cppcoro is in the works to “cover the time” between the current state of the art libraries (cppcoro) and c++23. What are your thoughts on this?
I know Jonathan Emmet wrote a piece 8 months ago on this topic of sorts.
I am looking for a VS2019 C++20 compatible coroutine library, even in limited format.
Thanks so much
I always used the reference to the coroutine’s argument explicitly for synchronization. (latch, std::promise… etc)
This way looks simple enough when we have to extend the return types (which has promise_type).