C++ coroutines: Waiting synchronously for our coroutine to complete

Raymond

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_await_suspend 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_await_resume 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.

2 comments

Comments are closed. Login to edit/delete your existing comments

  • Park Dong Ha

    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).

  • david shultis

    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