microsoft / microsoft/wil

Async cancellation bridge between coroutine and synchronous code

Open
#402 2 comments 0 reactions 1 assignee View on GitHub

@jonwis is already working on this.

Since Nov 30, 2023.

feature-request
Dominant language
C++
Stars
3k
Forks
300
Avg merge
19h 12m
Merged PRs (30d)
1

Description

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.