5

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

 3 years ago
source link: https://devblogs.microsoft.com/oldnewthing/20210430-00/?p=105171
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

Raymond Chen

Raymond

April 30th, 2021

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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK