{"id":106645,"date":"2022-05-11T07:00:00","date_gmt":"2022-05-11T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106645"},"modified":"2022-05-11T06:56:02","modified_gmt":"2022-05-11T13:56:02","slug":"20220511-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220511-00\/?p=106645","title":{"rendered":"On ways of finding out when a C++\/WinRT IAsyncAction has run to completion"},"content":{"rendered":"<p>A customer had a C++\/WinRT coroutine that just kept on running until you told it to stop.<\/p>\n<pre>struct Widget : winrt::implements&lt;Widget, IWidget&gt;\r\n{\r\nprivate:\r\n    winrt::Windows::Foundation::IAsyncAction m_work;\r\n\r\n    IAsyncAction DoStuffUntilCancelled()\r\n    {\r\n        co_await winrt::resume_background();\r\n\r\n        auto cancellation = co_await winrt::get_cancellation_token();\r\n        cancellation.enable_propagation();\r\n\r\n        \/\/ Run this loop until cancelled.\r\n        while (!cancellation()) {\r\n            one();\r\n            co_await two();\r\n            co_await three();\r\n\r\n            \/\/ pause a little bit before going again\r\n            co_await 1s;\r\n        }\r\n    }\r\n\r\npublic:\r\n    void Start()\r\n    {\r\n        m_work = DoStuffUntilCancelled();\r\n    }\r\n\r\n    void Stop()\r\n    {\r\n        m_work.Cancel();\r\n        ... wait until DoStuffUntilCancelled\r\n            has definitely stopped ...\r\n    }\r\n};\r\n<\/pre>\n<p>The idea is that you can <code>Start()<\/code> the Widget to make it begin doing something in the background, and you can <code>Stop()<\/code> it to make that thing stop.<\/p>\n<p>Let&#8217;s assume rules that say you can <code>Start()<\/code> a Widget at most once, and you cannot <code>Stop()<\/code> without a preceding <code>Stop()<\/code>. Because that&#8217;s not really the point of the exercise.<\/p>\n<p>The point of the exercise is to figure out how to implement the &#8220;wait until <code>Do\u00adStuff\u00adUntil\u00adCancelled<\/code> has definitely stopped&#8221; part.<\/p>\n<p>In the C++\/WinRT implementation of <code>IAsyncAction<\/code>, when the coroutine is cancelled, polling for cancellation will begin returning <code>true<\/code>, and any <code>co_await<\/code> operation will raise a cancellation exception. Furthermore, since we enabled cancellation propagation, if the coroutine is cancelled while it is suspended in a <code>co_await<\/code>, the cancellation will be propagated to the thing being <code>co_await<\/code>ed.<\/p>\n<p>The choice of expressing the background activity in the form of an <code>IAsyncAction<\/code> limits your ways of communicating with the coroutine. The only thing you can do to alter the behavior of a running <code>IAsyncAction<\/code> is to <code>Cancel()<\/code> it, which triggers the cancellation flow summarized above.<\/p>\n<p>So we&#8217;ll just have to deal with the cancellation.\u00b9<\/p>\n<p>One thing we could do is give the coroutine an event handle to signal, and have the coroutine set the event either when it chooses to exit on its own, or when it <a title=\"C++\/WinRT doesn't let your coroutine cheat death, but it does get to say good-bye\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200724-00\/?p=104005\"> observes the cancellation from within the coroutine<\/a>:<\/p>\n<pre>    IAsyncAction DoStuffUntilCancelled(HANDLE event)\r\n    {\r\n      <span style=\"color: blue;\">try {<\/span>\r\n        co_await winrt::resume_background();\r\n\r\n        auto cancellation = co_await winrt::get_cancellation_token();\r\n        cancellation.enable_propagation();\r\n\r\n        \/\/ Run this loop until cancelled.\r\n        while (!cancellation()) {\r\n            one();\r\n            co_await two();\r\n            co_await three();\r\n\r\n            \/\/ pause a little bit before going again\r\n            co_await 1s;\r\n        }\r\n        <span style=\"color: blue;\">SetEvent(event);\r\n      } catch (hresult_canceled const&amp;) {\r\n        SetEvent(event);\r\n        throw;\r\n      }<\/span>\r\n    }\r\n<\/pre>\n<p>Of course, this is more easily expressed as an RAII type.<\/p>\n<pre>    IAsyncAction DoStuffUntilCancelled(HANDLE event)\r\n    {\r\n        <span style=\"color: blue;\">auto setOnExit = wil::SetEvent_scope_exit(event);<\/span>\r\n\r\n        co_await winrt::resume_background();\r\n\r\n        auto cancellation = co_await winrt::get_cancellation_token();\r\n        cancellation.enable_propagation();\r\n\r\n        \/\/ Run this loop until cancelled.\r\n        while (!cancellation()) {\r\n            one();\r\n            co_await two();\r\n            co_await three();\r\n\r\n            \/\/ pause a little bit before going again\r\n            co_await 1s;\r\n        }\r\n    }\r\n<\/pre>\n<p>This alternative version has the advantage of also working if the coroutine is destroyed before running to completion. (C++\/WinRT coroutines don&#8217;t behave like this, but coroutines from some other library might.)<\/p>\n<p>Another pattern you often run into is wanting to stop the background coroutine when the Widget destructs. For that, you can take advantage of <a title=\"C++\/WinRT implementation extension points: abi_guard, abi_enter, abi_exit, and final_release\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191018-00\/?p=103010\"> the <code>final_<wbr \/>release<\/code> extension point<\/a>, which allows you to run extra code prior to destruction, possibly even itself a coroutine.<\/p>\n<pre>struct Widget : winrt::implements&lt;Widget, IWidget&gt;\r\n{\r\nprivate:\r\n    <span style=\"color: blue;\"><a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210312-00\/?p=104955\">awaitable_manual_reset_event<\/a> m_done;<\/span>\r\n    winrt::Windows::Foundation::IAsyncAction m_work;\r\n\r\n    IAsyncAction DoStuffUntilCancelled(<span style=\"color: blue;\">awaitable_manual_reset_event done<\/span>)\r\n    {\r\n        <span style=\"color: blue;\">auto setOnExit = wil::scope_exit([&amp;] { done.set(); });<\/span>\r\n\r\n        co_await winrt::resume_background();\r\n\r\n        auto cancellation = co_await winrt::get_cancellation_token();\r\n        cancellation.enable_propagation();\r\n\r\n        \/\/ Run this loop until cancelled.\r\n        while (!cancellation()) {\r\n            one();\r\n            co_await two();\r\n            co_await three();\r\n\r\n            \/\/ pause a little bit before going again\r\n            co_await 1s;\r\n        }\r\n    }\r\n\r\npublic:\r\n    void Start()\r\n    {\r\n        m_work = DoStuffUntilCancelled();\r\n    }\r\n\r\n    <span style=\"color: blue;\">static winrt::fire_and_forget final_release(std::unique_ptr&lt;Widget&gt; widget)\r\n    {\r\n        if (widget-&gt;m_work) {\r\n            widget-&gt;m_work.Cancel();\r\n\r\n            \/\/ make sure coroutine has exited before we destruct\r\n            co_await widget-&gt;m_done;\r\n        }\r\n    }<\/span>\r\n};\r\n<\/pre>\n<p>\u00b9 Another way out is to change the game. Instead of returning an <code>IAsyncAction<\/code>, return an Operation object, with methods to request a stop, and to wait for the stop to complete.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Just some noodling on ideas.<\/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-106645","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Just some noodling on ideas.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106645","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=106645"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106645\/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=106645"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106645"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106645"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}