C++/WinRT coroutines use the Completed
delegate property to be notified when an asynchronous operation is complete. There are multiple parts of the completion handler. Today we’ll look at an oversimplified version, and then we will gradually build it up.
template <typename Async> struct await_adapter { await_adapter(Async const& async) : async(async) { } Async const& async; bool await_ready() const noexcept { return false; } void await_suspend(std::experimental::coroutine_handle<> handle) const { async.Completed([handle](auto&& ...) { handle.resume(); }); } auto await_resume() const { return async.GetResults(); } };
To co_await
an IAsyncAction
or IAsyncOperation
, we register a completion callback that resumes the awaiting coroutine. When the coroutine resumes, it will call await_resume()
to obtain the result of the co_await
, and we propagate the result of the GetResults()
method.
We don’t particularly care about the parameters passed to the completion delegate: Those tell us whether the asynchronous work completed successfully, was cancelled, or failed outright, but we don’t need to remember that information because GetResults()
will report the information again: If the asynchronous work did not complete successfully, then GetResults()
will thrown an exception describing why it was not successful.
As things go, this is a fairly standard implementation of a coroutine awaiter, although there is a race condition we need to fix:
void await_suspend(std::experimental::coroutine_handle<> handle) const
{
auto extend_lifetime = async;
async.Completed([handle](auto&& ...)
{
handle.resume();
});
}
If the completion handler runs before Completed()
returns, then we end up destroying the async
while there is still an active call on it. This is a problem I called out at the start of my coroutine series. To fix this, we make a local copy of the async
to extend its lifetime to the end of the await_suspend
function.
This is just the starting point for C++/WinRT coroutine completion handlers. Next time, we’ll add apartment-preserving behavior.
Is it undefined behavior to simply have destroyed during the execution of a non-static member function, even if that function no longer accesses ?
<code>
If this is not UB, then shouldn't need ; we can assume itself is written correctly to not use (which is our ) after scheduling for to execute, and we don't need to extend 's lifetime ourselves because we don't access any more.
That aside, calling the destructor from within a non-static member function is, on its own, legal. Of course, this is extremely prone to making subsequent code exhibit UB, such as in this case.
There is no requirement that Async::Completed avoid *this after calling the handler. In fact, if the Async is remote, it will definitely access *this, because will have to tear down the channel and proxy.