July 17th, 2024

Creating an already-completed asynchronous activity in C++/WinRT, part 7

So far, we’ve been building helpers for producing already-completed asynchronous activities by creating coroutines that immediately succeed or fail. But really, coroutines are a red herring. What we want is an object that implements an asynchronous interface which models an already-completed asynchronous activity. In other words, we don’t have to be a coroutine. We just have to be an interface.

As before, we’ll start with a special case to make sure we understand the problem, and then we’ll generalize it.

struct completed_action :
    winrt::implements<completed_action,
        winrt::Windows::Foundation::IAsyncAction,
        winrt::Windows::Foundation::IAsyncInfo>
{
    using namespace winrt::Windows::Foundation;

    winrt::slim_mutex m_mutex;
    winrt::AsyncActionCompletedHandler m_completed;

    // IAsyncInfo
    auto Id() { return 1; }
    auto Status() { return AsyncStatus::Completed; }
    auto ErrorCode() { return S_OK; }
    auto Cancel() { }
    auto Close() { }

    // IAsyncAction
    auto Completed()
    {
        winrt::slim_lock_guard lock(m_mutex);
        return m_completed;
    }
    auto Completed(winrt::AsyncActionCompletedHandler const& handler)
    {
        {
            winrt::slim_lock_guard lock(m_mutex);
            if (m_completed) {
                throw winrt::hresult_illegal_delegate_assignment();
            }
            m_completed = handler;
        }
        handler(*this, Status());
    }
    auto GetResults() { }
};

This implements the IAsync­Action contract in a very simple way: It is always Completed, there is no error, you can’t cancel or close it, it has no results, and it immediately invokes the Completed handler when set.

The most complicated part is the Completed handler because it’s nearly all just bookkeeping. First, we enforce the rule that you can set the handler only once. And then we remember the handler so that we can return it if anybody ever asks for it. (Narrator: Nobody ever asks for it.) The only real work is invoking the handler immediately to tell it “I’m already done!”

We can use this already-completed action when have something that can complete synchronously, but the interface expects an asynchronous action.

winrt::IAsyncAction SetNameAsync(winrt::hstring const& name)
{
    m_name = name;
    return winrt::make<completed_action>();
}

For an already-completed IAsync­Operation<T>, everything would be the same, except that our GetResults() returns the already-completed value.

And if we are implementing a -With­Progress, then we also need a Progress delegate property that we never call, but nevertheless have to remember in case the caller reads the property back.

    winrt::AsyncActionProgressHandler<P> m_progress;

    auto Progress()
    {
        winrt::slim_lock_guard lock(m_mutex);
        return m_progress;
    }
    auto Progress(winrt::AsyncActionProgressHandler<P> const& handler)
    {
        winrt::slim_lock_guard lock(m_mutex);
        m_progress = handler;
    }

Next time, we’ll write a generalized version, now that we understand the pattern.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

2 comments

Discussion is closed. Login to edit/delete existing comments.

  • 紅樓鍮

    Since std::atomic<std::shared_ptr<T>> exists, would it make sense to also have atomic versions of C++/WinRT projection types and winrt::com_ptr<T>?

    • Me Gusta · Edited

      There is no need.
      The C++/WinRT com_ptr and projection types don’t contain a reference count and the standard C++/WinRT object implementation is already atomic. The definition of the reference count in root_implements is:

      std::atomic<std::conditional_t<is_weak_ref_source::value, uintptr_t, uint32_t>> m_references{ 1 };