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

512 Views Asked by At

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.

3

There are 3 best solutions below

0
Tom Huntington On BEST ANSWER

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.

0
Tom Huntington On

Edit: In hindsight, I could have just used CreateThreadpoolWait and never bothered with coroutines.

The performance of concurrency::task is very good, so that may have been a better solution to my problem, but I'm familiar with cppwinrt coroutines and they provide a very convenient winrt::resume_on_signal which I will use.

If you want to learn how to implement coroutine promise types I recommend watching James McNellis' talk.

For how cppwinrt implements their asynchrony with coroutines watch https://www.youtube.com/watch?v=v0SjumbIips (they do it by specializing coroutine_traits)

template <typename... Args>
    struct coroutine_traits<winrt::Windows::Foundation::IAsyncAction, Args...> {...}

But I just followed Raymond Chen's advice and wrote a wrapper around IInspectable

template<typename T>
struct IInspectableWrapper : winrt::implements<IInspectableWrapper<T>, winrt::Windows::Foundation::IInspectable>
{
    IInspectableWrapper(T state) : state_(state) {}

    static winrt::Windows::Foundation::IInspectable box(T state)
    { 
        return winrt::make<IInspectableWrapper>(state);
    }
    static T unbox(const Windows::Foundation::IInspectable& inspectable)
    {
        return winrt::get_self<IInspectableWrapper>(inspectable)->state_;
    }

private:
    T state_;
};

Which can be used

winrt::Windows::Foundation::IAsyncOperation<IInspectable> my_coroutine()
    {
        co_return IInspectableWrapper<int>::box(1);
    }

...

auto boxed_result = co_await my_coroutine();
auto result = IInspectableWrapper<int>::unbox(boxed_result);

Although the unboxing is unsafe.

2
Tom Huntington On

Also you could just pass an out parameter to you coroutine and return an IAsyncAction

winrt::Windows::Foundation::IAsyncAction my_coroutine3(std::weak_ptr<int> weak_i)
{
    if(auto shared_i = weak_i.lock())
        *shared_i = 1;
}
...
auto i = std::make_shared<int>();
auto action = my_coroutine3(i);
auto future = std::tuple{std::move(i), std::move(action)};