{"id":107191,"date":"2022-09-16T07:00:00","date_gmt":"2022-09-16T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107191"},"modified":"2022-09-16T06:12:40","modified_gmt":"2022-09-16T13:12:40","slug":"20220916-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220916-00\/?p=107191","title":{"rendered":"Serializing asynchronous operations in C++\/WinRT, gotchas and final assembly"},"content":{"rendered":"<p>Last time, <a title=\"Serializing asynchronous operations in C++\/WinRT\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220915-00\/?p=107182\"> we came up with a way of making asynchronous operations run in sequence<\/a>, but I noted that there were some gotchas we need to work through.<\/p>\n<p>One gotcha is cancellation.<\/p>\n<p>In the C++\/WinRT implementation of Windows Runtime asynchronous operations, cancellation of an asynchronous operation <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200724-00\/?p=104005\"> trigger the completion callback immediately<\/a>, without waiting for the coroutine to acknowledge the cancellation. If we had used the <code>Completed<\/code> callback to detect the completion of the coroutine, we would have started running the next coroutine before the previous one cleaned up from its cancellation.<\/p>\n<p>Good thing we aren&#8217;t doing that. We trigger the next coroutine at the destruction of the <code>chained_<wbr \/>task_<wbr \/>completer<\/code>, which we arranged to destruct last, so everything else in the coroutine has already destructed. (Well, the parameters haven&#8217;t been destructed yet, but they were all references, so there was nothing to destruct.)<\/p>\n<p>The other thing to worry about is coroutine destruction. That&#8217;s where you take a suspended coroutine and call <code>destroy<\/code> on it. This basically rips the rug out of the coroutine and destructs everything in it without letting any of the coroutine body run anything. If that happens to our coroutine while suspended at the <code>co_await lazy_start<\/code>, then the <code>chained_<wbr \/>task_<wbr \/>completer<\/code> will destruct and start running the next coroutine prematurely.<\/p>\n<p>Fortunately, C++\/WinRT coroutines are never abandoned. They always run to completion (possibly by throwing an exception). So at least for C++\/WinRT coroutines, we don&#8217;t have to worry about this.\u00b9<\/p>\n<p>What if the <code>task_<wbr \/>sequencer<\/code> is destructed while there are still pending coroutines?<\/p>\n<p>When the <code>task_<wbr \/>sequencer<\/code> destructs, the <code>m_latest<\/code> shared pointer destructs, which means that the last node in the chain now has only one strong reference, namely the strong reference from the last coroutine in the chain:<\/p>\n<p>\n<code>coroutine<\/code><br \/>\n<code>chained_task<\/code><\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" title=\"The linked list from last time, where the linked list of alternating coroutines and chained_task nodes ends with a 'chained_task', but this time the 'm_latest' is crossed out, so the only inbound arrow is from the preceding coroutine.\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\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>\u00d7<br \/>\n\u2191<br \/>\n<code>m_latest\n    <\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>As the coroutines complete, the nodes come off the head of the linked list, and when the last one completes, the last <code>chained_<wbr \/>task<\/code> destructs. Everything is cleaned up.<\/p>\n<p>Now we can take our <code>task_<wbr \/>sequencer<\/code> for a spin.<\/p>\n<pre>task_sequencer sequence;\r\n\r\nwinrt::Windows::Foundation::IAsyncOperation&lt;winrt::hstring&gt;\r\nMessageAfterDelayAsync(\r\n    winrt::hstring message,\r\n    winrt::Windows::Foundation::TimeSpan delay)\r\n{\r\n    co_await winrt::resume_after(delay);\r\n    std::wcout &lt;&lt; message.c_str() &lt;&lt; std::endl;\r\n    co_return message + L\" done\";\r\n}\r\n\r\nauto AddMessageAfterDelayAsync(\r\n    winrt::hstring message,\r\n    winrt::Windows::Foundation::TimeSpan delay)\r\n{\r\n    return sequence.QueueTaskAsync(\r\n        [=] { return MessageAfterDelayAsync(message, delay); });\r\n}\r\n\r\nvoid do_in_sequence()\r\n{\r\n    auto first = AddMessageAfterDelayAsync(L\"first\", 1s);\r\n    auto second = AddMessageAfterDelayAsync(L\"second\", 1s);\r\n    auto third = AddMessageAfterDelayAsync(L\"third\", 1s);\r\n\r\n    \/\/ Cancel the second one, just for fun.\r\n    second.Cancel();\r\n\r\n    \/\/ wait for the third one.\r\n    auto third_message = third.get();\r\n\r\n    \/\/ print the results\r\n    std::wcout &lt;&lt; first.get().c_str() &lt;&lt; std::endl;\r\n    std::wcout &lt;&lt; third_message.c_str() &lt;&lt; std::endl;\r\n}\r\n<\/pre>\n<p>\u00b9 In general, abandonment of a coroutine via premature <code>destroy()<\/code> is not something most coroutine libraries deal with, so at least we&#8217;re in good company.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Cancellation and abandonment.<\/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-107191","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Cancellation and abandonment.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107191","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=107191"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107191\/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=107191"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107191"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107191"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}