c++-winrtc++-coroutinecppwinrt

(IAsyncOperation for non WinRT types) Run coroutine task on Windows thread pool


I'm trying to provide co-routine support for non WinRT types that will asynchronously execute on the Windows thread pool.

cppcoro and libunifex both provide the equivalent coroutine task type task<T>. I imagine it would be a good idea to run these tasks on the windows thread pool, but maybe I'm just being silly and should just use one of the thread pools provided these libraries.

On the other hand, I was hoping to see how cppwinrt handles this but I cant penetrate definition of IAsyncOperation

template <typename TResult>
    struct __declspec(empty_bases) IAsyncOperation :
        winrt::Windows::Foundation::IInspectable,
        impl::consume_t<winrt::Windows::Foundation::IAsyncOperation<TResult>>,
        impl::require<winrt::Windows::Foundation::IAsyncOperation<TResult>, winrt::Windows::Foundation::IAsyncInfo>
    {
        static_assert(impl::has_category_v<TResult>, "TResult must be WinRT type.");
        IAsyncOperation(std::nullptr_t = nullptr) noexcept {}
        IAsyncOperation(void* ptr, take_ownership_from_abi_t) noexcept : winrt::Windows::Foundation::IInspectable(ptr, take_ownership_from_abi) {}
    };

Kenny Kerr did a talk at cppcon 2016 on this so maybe ill try watching that and then try greping the source code/generated headers.


Solution

  • The most hardest part about learning to implement coroutines is disambiguating the difference between awaitable types and coroutine promise types.

    concept awaitable:
        await_ready() -> bool
        await_suspend(coroutine_handle<> handle) -> void
        await_resume() -> void
    
    concept promise_type:
        get_return_object() -> coroutine_return_type<T>
        return_value(T const& v) -> void
        unhandled_exception() -> void
        initial_suspend() -> awaitable
        final_suspend() noexcept -> awaitable
    

    For now, I only need to poll if the object returned by my coroutine is ready (rather than co_await it), but I need to be able to await on winrt::resume_on_signal in the coroutine. cppwinrt does the work implementing the awaitable type in winrt::resume_on_signal and I need to do the work implementing the promise type returned by the coroutine I call winrt::resume_on_signal in.

    The simplest possible type you could return from a coroutine might be std::shared_ptr<std::optional<T> and specializing coroutine_traits allows you to do this:

    namespace std::experimental
    {
    template <typename T, typename... Args>
    struct coroutine_traits<std::shared_ptr<std::optional<T>>, Args...>
    {
        struct promise_type
        {
            using result_type = std::shared_ptr<std::optional<T>>;
            result_type result = std::make_shared<std::optional<T>>(std::nullopt);
    
            result_type get_return_object() {  return result; }
            void return_value(T const& v) {  *result = std::optional<T>(v); }
            void unhandled_exception() { assert(0); }
            std::experimental::suspend_never initial_suspend() { return{}; }
            std::experimental::suspend_never final_suspend() noexcept { return{}; }
        };
    };
    }
    

    which you can use as

    std::shared_ptr<std::optional<int>> my_coroutine2(HANDLE h)
        {
            co_await winrt::resume_on_signal(h);
            co_return int(1);
        }
    ...
    auto shared_optional = my_coroutine2();
    if (*shared_optional)
    {
        auto result = **shared_optional;
    }
    

    However, there are caveats. You will create a race condition if you also make this an awaitable type oldnewthing, although I don't beleive you even could make it awaitable because you need to be able call a continuation when the value is set. And it doesn't handle exceptions.