Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async cancellation bridge between coroutine and synchronous code #402

Open
jonwis opened this issue Nov 30, 2023 · 2 comments
Open

Async cancellation bridge between coroutine and synchronous code #402

jonwis opened this issue Nov 30, 2023 · 2 comments
Assignees
Labels
feature-request New feature or request

Comments

@jonwis
Copy link
Member

jonwis commented Nov 30, 2023

We have code that takes a shared_ptr<bool> as a "cancel marker" for when an outer IAsyncOperation calls an inner long-running-but-non-async method, like this:

winrt::IAsyncOperation<winrt::hstring> GetStringOfManyThingsAsync() {
    auto lifetime{get_strong()};
    auto sharedCancel = std::make_shared<bool>(false);
    auto canceltoken = co_await winrt::get_cancellation_token();
    canceltoken.enable_propagation(); // not _strictly_  necessary since this does not await anything else
    canceltoken.callback([sharedCancel] { *sharedCancel = true; });
    co_await winrt::resume_background();

    auto moreThings = CallOtherCodeSynchronously(..., sharedCancel);
    
    co_return StringFromMoreThigns(moreThings);
}

auto CallOtherCodeSynchronously(auto... std::shared_ptr<bool> cancelToken) {
    for (int i = 0; i < 200 && !*cancelToken; ++i) {
        DoSlowThing();
    }
    return ...;    
}

It'd be neat if instead we could say this:

winrt::IAsyncOperation<winrt::hstring> GetStringOfManyThingsAsync() {
    auto lifetime{get_strong()};
    auto canceltoken = co_await wil::get_shared_cancellation_token();
    canceltoken.enable_propagation(); // not _strictly_  necessary since this does not await anything else
    co_await winrt::resume_background();

    auto moreThings = CallOtherCodeSynchronously(..., canceltoken);
    
    co_return StringFromMoreThigns(moreThings);
}

auto CallOtherCodeSynchronously(auto... std::shared_ptr<bool> cancelToken) {
    for (int i = 0; i < 200 && !*cancelToken; ++i) {
        DoSlowThing();
    }
    return ...;    
}

Or maybe instead we say this:

auto canceltoken = co_await winrt::get_cancellation_token();
auto sharedCanceller = wil::make_shared_cancel(canceltoken);

... where sharedCanceller is basically this:

struct shared_cancel_token
{
    bool m_canceled{false};
    void cancel() { m_canceled = true; }
    bool is_canceled() const { return m_canceled };
}
template<typename Q> std::shared_ptr<shared_cancel_token> make_shared_cancel(Q& outerToken)
{
    auto t = std::make_shared<shared_cancel_token>();
    outerToken.callback([t] { t->cancel(); };
    return t;
}

And then code passes around the shared_cancel_token instead of the shared_ptr. Then we could also do things like hang a wait() off of that using WaitOnAddress (expanding the size of m_canceled to be a pointer) or expose an event handle for use with WFMO.

@jonwis jonwis added the feature-request New feature or request label Nov 30, 2023
@jonwis
Copy link
Member Author

jonwis commented Nov 30, 2023

Or maybe it's make_cancellation_adapter since it's adapting between async & sync?

@jonwis jonwis self-assigned this Nov 30, 2023
@jonwis
Copy link
Member Author

jonwis commented Nov 30, 2023

Oh, maybe this is easy:

struct shared_cancel_token
{
    bool is_cancelled() const
    {
        return m_cancelled->is_signaled();
    }

    void cancel()
    {
        m_cancelled->SetEvent();
    }

    bool wait_for(std::optional<std::chrono::milliseconds> timeout = std::nullopt)
    {
        if (timeout)
        {
            return m_cancelled->wait(timeout->count());
        }
        else
        {
            return m_cancelled->wait();
        }
    }

private:
    std::shared_ptr<wil::slim_event_manual_reset> m_cancelled{std::make_shared<wil::slim_event_manual_reset>()};
};

shared_cancel_token make_shared_cancel_token(auto&& sourceToken)
{
    shared_cancel_token t;
    sourceToken.callback([t]() mutable { t.cancel(); });
    return t;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant