{"id":107182,"date":"2022-09-15T07:00:00","date_gmt":"2022-09-15T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107182"},"modified":"2022-09-15T07:14:16","modified_gmt":"2022-09-15T14:14:16","slug":"20220915-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220915-00\/?p=107182","title":{"rendered":"Serializing asynchronous operations in C++\/WinRT"},"content":{"rendered":"<p>Armed with some insight into C++ coroutines and lazy-start coroutines, we return to the issue of making asynchronous operations run one after the other instead of concurrently.<\/p>\n<p>Adapting the solution from C# comes with a frustrating complication: Unlike C# <code>Task<\/code>s, C++\/WinRT <code>IAsyncAction<\/code> and <code>IAsyncOperation<\/code> support only one completion callback, so we cannot <code>co_await<\/code> them more than once. That&#8217;s too bad, because the C# version relied on having two awaiters: One is the ultimate caller of the asynchronous method, and the other is the internal awaiter that we use for sequencing.<\/p>\n<p>Since the caller is going to perform a <code>co_await<\/code>, our internal awaiter will have to use some other mechanism for sequencing. We&#8217;ll do that by hooking up the continuations manually into our own data structures.<\/p>\n<pre>struct task_sequencer\r\n{\r\n    task_sequencer() = default;\r\n    task_sequencer(const task_sequencer&amp;) = delete;\r\n    void operator=(const task_sequencer&amp;) = delete;\r\n\r\nprivate:\r\n    using coro_handle = std::experimental::coroutine_handle&lt;&gt;;\r\n\r\n    struct suspender\r\n    {\r\n        bool await_ready() const noexcept { return false; }\r\n        void await_suspend(coro_handle h)\r\n            noexcept { handle = h; }\r\n        void await_resume() const noexcept { }\r\n\r\n        coro_handle handle;\r\n    };\r\n\r\n    static void* completed()\r\n    { return reinterpret_cast&lt;void*&gt;(1); }\r\n\r\n    struct chained_task\r\n    {\r\n        chained_task(void* state = nullptr) : next(state) {}\r\n\r\n        void continue_with(coro_handle h) {\r\n            if (next.exchange(h.address(),\r\n                        std::memory_order_acquire) != nullptr) {\r\n                h();\r\n            }\r\n        }\r\n\r\n        void complete() {\r\n            auto resume = next.exchange(completed());\r\n            if (resume) {\r\n                coro_handle::from_address(resume).resume();\r\n            }\r\n        }\r\n\r\n        std::atomic&lt;void*&gt; next;\r\n    };\r\n\r\n    struct completer\r\n    {\r\n        ~completer()\r\n        {\r\n            chain-&gt;complete();\r\n        }\r\n        std::shared_ptr&lt;chained_task&gt; chain;\r\n    };\r\n\r\n    winrt::slim_mutex m_mutex;\r\n    std::shared_ptr&lt;chained_task&gt; m_latest =\r\n        std::make_shared&lt;chained_task&gt;(completed());\r\n\r\npublic:\r\n    template&lt;typename Maker&gt;\r\n    auto QueueTaskAsync(Maker&amp;&amp; maker) -&gt;decltype(maker())\r\n    {\r\n        auto current = std::make_shared&lt;chained_task&gt;();\r\n        auto previous = [&amp;]\r\n        {\r\n            winrt::slim_lock_guard guard(m_mutex);\r\n            return std::exchange(m_latest, current);\r\n        }();\r\n\r\n        suspender suspend;\r\n\r\n        using Async = decltype(maker());\r\n        auto task = [](auto&amp;&amp; current, auto&amp;&amp; makerParam,\r\n                       auto&amp;&amp; contextParam, auto&amp; suspend)\r\n                    -&gt; Async\r\n        {\r\n            completer completer{ std::move(current) };\r\n            auto maker = std::move(makerParam);\r\n            auto context = std::move(contextParam);\r\n\r\n            co_await suspend;\r\n            co_await context;\r\n            co_return co_await maker();\r\n        }(current, std::forward&lt;Maker&gt;(maker),\r\n          winrt::apartment_context(), suspend);\r\n\r\n        previous-&gt;continue_with(suspend.handle);\r\n\r\n        return task;\r\n    }\r\n};\r\n<\/pre>\n<p>There&#8217;s a lot going on here, so let&#8217;s take it bit by bit.<\/p>\n<pre>struct task_sequencer\r\n{\r\n    task_sequencer() = default;\r\n    task_sequencer(const task_sequencer&amp;) = delete;\r\n    void operator=(const task_sequencer&amp;) = delete;\r\n<\/pre>\n<p>Our <code>task_sequencer<\/code> is default-constructible but is not copyable or assignable.<\/p>\n<p>Inside the class, we start with the <code>suspender<\/code>, which <!-- backref: Creating a manual-start C++\/WinRT coroutine from an eager-start one, part 2 --> we saw last time. We use this to force the lambda coroutine (coming later) to suspend and capture the coroutine handle that lets us resume it.<\/p>\n<p>Next, we have the <code>chained_task<\/code>. This class connects the coroutines that want to run in sequence.<\/p>\n<div id=\"p20220915_head\" style=\"display: none;\">\u00a0<\/div>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" title=\"A linked list of alternating coroutines and chained_task nodes. The 'current' of the coroutine points to the next 'chained_task', and the 'next' of 'chained_task' points to the next coroutine. The chain ends with a 'chained_task' (labeled 'm_latest') whose 'next' is null.\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>coroutine<\/code><\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>current<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2199\ufe0e<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>coroutine<\/code><\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\"><code>current<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<td>\u2192<\/td>\n<td><code>nullptr<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2191<br \/>\n<code>m_latest<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Each coroutine has a local variable called <code>current<\/code> which is a shared pointer to a <code>chained_task<\/code>. That <code>chained_task<\/code> has a member called <code>next<\/code> which points to the coroutine to run after the current coroutine has completed. The chain ends with a <code>chained_task<\/code> (also known as <code>m_latest<\/code>) whose <code>next<\/code> is null.<\/p>\n<p>Each <code>chained_task<\/code> remembers the coroutine that needs to run next in its <code>next<\/code> member. The most recently-queued one is remembered in <code>m_latest<\/code>, and its <code>next<\/code> is <code>nullptr<\/code> if the coroutine is still running, or is <code>completed<\/code> if the coroutine has completed (and is waiting for somebody to run next).<\/p>\n<p>We set our initial condition by initializing <code>m_latest<\/code> to a <code>chained_task<\/code> that has already completed. That way, the next coroutine to be queued will run immediately.<\/p>\n<p>First, we create a new <code>chained_task<\/code> node and make it the <code>m_latest<\/code>, while saving the previous value of <code>m_latest<\/code> in <code>previous<\/code>.<\/p>\n<pre>    auto current = std::make_shared&lt;chained_task&gt;();\r\n    auto previous = [&amp;] {\r\n        winrt::slim_lock_guard guard(m_mutex);\r\n        return std::exchange(m_latest, current);\r\n    }();\r\n<\/pre>\n<p>In pictures, we&#8217;ve created this:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" title=\"A new 'chained_task' node has been created but not linked into the linked list. The 'm_latest' now refers to the new node.\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>coroutine<\/code><\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>current<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2199\ufe0e<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>coroutine<\/code><\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\"><code>current<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<td>\u2192<\/td>\n<td><code>nullptr<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<td>\u2192<\/td>\n<td><code>nullptr<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2191<br \/>\n<code>m_latest<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Once we&#8217;ve set up the new node, we capture it into a coroutine which represents the queued task, and then use the <code>suspender<\/code> in order to suspend the coroutine immediately and obtain its coroutine handle.<\/p>\n<pre>    suspender suspend;\r\n\r\n    using Async = decltype(maker());\r\n    auto task = [](auto&amp;&amp; current, ..., auto&amp; suspend)\r\n                -&gt; Async\r\n    {\r\n        chained_task_completer completer{ std::move(current) };\r\n        ...\r\n\r\n        co_await suspend;\r\n        ...\r\n    }(current, ..., suspend);\r\n<\/pre>\n<p>This fills in another par of the diagram:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" title=\"A new 'coroutine' node has been created, and its 'current' points to the 'chained_task' we have just created.\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>coroutine<\/code><\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>current<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2199\ufe0e<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>coroutine<\/code><\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\"><code>current<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<td>\u2192<\/td>\n<td><code>nullptr<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>coroutine<\/code><\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\"><code>current<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<td>\u2192<\/td>\n<td><code>nullptr<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2191<br \/>\n<code>m_latest<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>And then we take that coroutine handle and hook it up to the previous <code>chained_task<\/code>:<\/p>\n<pre>    previous-&gt;continue_with(suspend.handle);\r\n<\/pre>\n<p>That last step links everything together again:<\/p>\n<p>The linked list now looks like this:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" title=\"The 'next' of the last 'chained_task' in the linked list now points to the coroutine we just created, making the diagram match what we started, but with a new coroutine and 'chained_task' at the end.\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>coroutine<\/code><\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>current<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2199\ufe0e<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>coroutine<\/code><\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\"><code>current<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2199\ufe0e<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>coroutine<\/code><\/td>\n<td>&nbsp;<\/td>\n<td><code>chained_task<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px gray;\"><code>current<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px gray;\"><code>next<\/code><\/td>\n<td>\u2192<\/td>\n<td><code>nullptr<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2191<br \/>\n<code>m_latest<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>As a special case, if the previously-final <code>chained_task<\/code> has already completed, then instead of hooking up the arrow from that <code>chained_task<\/code> to the latest coroutine, we just resume the latest coroutine immediately.<\/p>\n<p>That&#8217;s how nodes get added to the list. But how are they removed?<\/p>\n<p>The nodes disappear from the front of the list when coroutines complete. When a coroutine completes, its <code>completer<\/code> destructs, which destructs the shared pointer to the <code>chained_<wbr \/>task<\/code>. If the <code>chained_<wbr \/>task<\/code> is not the <code>m_latest<\/code>, then this destroys the last shared pointer to the <code>chained_<wbr \/>task<\/code>, so it too destructs. As coroutines complete, the head of the linked list gets gobbled up until only <code>m_latest<\/code> remains.<\/p>\n<p>Now let&#8217;s look inside the coroutine we created.<\/p>\n<pre>    using Async = decltype(maker());\r\n    auto task = [](auto&amp;&amp; current, auto&amp;&amp; makerParam,\r\n                   auto&amp;&amp; contextParam, auto&amp; suspend)\r\n                -&gt; Async\r\n    {\r\n        completer completer{ std::move(current) };\r\n        auto maker = std::move(makerParam);\r\n        auto context = std::move(contextParam);\r\n\r\n        co_await suspend;\r\n        co_await context;\r\n        co_return co_await maker();\r\n    }(current, std::forward&lt;Maker&gt;(maker),\r\n      winrt::apartment_context(), suspend);\r\n<\/pre>\n<p>Again, there are a few things going on here.<\/p>\n<p>This is a captureless lambda because coroutine lambdas with captures are scary. The captures are instead passed as explicit parameters so they go into the coroutine frame.<\/p>\n<p>The first thing we do is move the objects out of the parameters into locals, so that they destruct as soon as the coroutine completes. We saw some time ago that <!-- backref: Local variables are different from parameters in C++ coroutines --> coroutine parameters do not destruct until the coroutine is destroyed, but we want to resume the next coroutine in the chain as soon as the previous one completes.<\/p>\n<p>We create the <code>completer<\/code> as the first local variable so that it destructs last. That way, we resume the next coroutine only after the current one has destructed everything it had captured. We use an object with a destructor to ensure that chaining to the next coroutine occurs even if the current coroutine exits with an exception.<\/p>\n<p>The <code>maker<\/code> is moved into a local variable so that it (and all of its own captures) destructs as soon as the coroutine completes, rather than lingering until the coroutine is destroyed.<\/p>\n<p>We also move the <code>apartment_context<\/code> into a local variable so that we can switch back to that context once we are resumed. The previous coroutine may have completed on a different COM context, and we need to start the next one in the original context.<\/p>\n<p>When the coroutine completes (either normally or via an exception), the <code>completer<\/code> destructor resume the next coroutine in the chain, if one exists. If not, it just marks itself as complete so that when the next coroutine shows up, it knows it should run immediately.<\/p>\n<p>The atomic operation for publishing the coroutine handle to <code>next<\/code> uses release semantics so that all of the coroutine state generated by the current thread are made visible before we publish the coroutine handle. Conversely, the exchange operation that obtains the coroutine handle uses acquire semantics to ensure that the processor uses the published values instead of locally-cached ones.<\/p>\n<p>Note that if your object is single-threaded, and tasks can be queued only from a single thread, then you don&#8217;t need the <code>m_mutex<\/code>, which also simplifies the updating of <code>m_latest<\/code>:<\/p>\n<pre>    auto current = std::make_shared&lt;chained_task&gt;();\r\n    auto previous = std::exchange(m_latest, current);\r\n<\/pre>\n<p>But wait, we&#8217;re not done yet. There are some exceptional conditions we&#8217;ll look at next time.<\/p>\n<p>\n<script>\nwindow.addEventListener(\"load\", function() {\n  var fullFF = getComputedStyle(document.body).fontFamily;\n  var simpleFF = fullFF.replace(\/ Emoji\/g, \"\");\n  \/\/ break up \"style\" to prevent wordpress from injecting random junk\n  document.getElementById(\"p20220915_head\").innerHTML =\n`<s` + `tyle>\nbody { font-family: ${simpleFF}; }\n.emoji { font-family: ${fullFF}; }\n.entry-content th { padding: 1px; } \/* stylesheet workaround *\/\n.entry-content td { padding: 1px; } \/* stylesheet workaround *\/\n<\/s` + `tyle>`;\n});\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Making them run one after the other.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-107182","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Making them run one after the other.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107182","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=107182"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107182\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=107182"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107182"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107182"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}