{"id":104005,"date":"2020-07-24T07:00:00","date_gmt":"2020-07-24T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104005"},"modified":"2020-07-24T08:04:25","modified_gmt":"2020-07-24T15:04:25","slug":"20200724-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200724-00\/?p=104005","title":{"rendered":"C++\/WinRT doesn&#8217;t let your coroutine cheat death, but it does get to say good-bye"},"content":{"rendered":"<p>I noted in a footnote that <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200722-00\/?p=103997\"> it&#8217;s important to use RAII types for all of your cleanup in a coroutine<\/a> because cancellation will cause the coroutine to abruptly stop executing at the next suspension point. C++\/WinRT doesn&#8217;t let your coroutine cheat death, but it does get to say good-bye.<\/p>\n<p>C++\/WinRT reports cancellation to the coroutine by throwing an <code>hresult_canceled<\/code> exception in the context of the coroutine itself. This takes advantage of the existing coroutine machinery to stow exceptions in the asynchronous operation so they can be observed by the code that is awaiting the result.<\/p>\n<p>But it also means that you can see the exception fly past as you proceed inexorably to your death.<\/p>\n<pre>IAsyncOperation&lt;int&gt; DoSomethingAsync()\r\n{\r\n    try {\r\n        \/* do stuff *\/\r\n        co_return 42;\r\n    } catch (hresult_canceled const&amp;) {\r\n        printf(\"Pardonnez-moi, monsieur, je ne l'ai pas fait expr\u00e8s.\");\r\n        throw;\r\n    }\r\n}\r\n<\/pre>\n<p>You can catch the <code>hresult_<wbr \/>canceled<\/code> exception and perhaps do some final cleanup. When you&#8217;re done, you rethrow the exception to allow normal cleanup of the coroutine to proceed.<\/p>\n<p>Of course, if your intention is to perform actual cleanup (rather than merely making a statement for posterity),\u00b9 then you&#8217;ll need to catch <i>all<\/i> exceptions, not just cancellation.<\/p>\n<pre>IAsyncOperation&lt;int&gt; DoSomethingAsync()\r\n{\r\n    ClaimWidget();\r\n    try {\r\n        \/* do stuff *\/\r\n        co_return 42;\r\n    } catch (...) {\r\n        DisclaimWidget();\r\n        throw;\r\n    }\r\n}\r\n<\/pre>\n<p>At this point, you may as well just use an RAII type.<\/p>\n<pre>struct ScopedWidgetClaim\r\n{\r\n    ScopedWidgetClaim() { ClaimWidget(); }\r\n    ~ScopedWidgetClaim() { DisclaimWidget(); }\r\n\r\n    \/\/ non-copyable, non-assignable\r\n    ScopedWidgetClaim(ScopedWidgetClaim const&amp;) = delete;\r\n    void operator=(ScopedWidgetClaim const&amp;) = delete;\r\n};\r\n\r\nIAsyncOperation&lt;int&gt; DoSomethingAsync()\r\n{\r\n    ScopedWidgetClaim claim;\r\n    \/* do stuff *\/\r\n    co_return 42;\r\n}\r\n<\/pre>\n<p>Maybe you think you can cheat death by swallowing the exception instead of rethrowing it?<\/p>\n<pre>IAsyncOperation&lt;int&gt; DoSomethingAsync()\r\n{\r\n    try {\r\n        \/* do stuff *\/\r\n        co_return 42;\r\n    } catch (hresult_canceled const&amp;) {\r\n        co_return 0;\r\n    }\r\n}\r\n<\/pre>\n<p>What happens here depends on where the cancellation exception was generated. If it was the result of a dependent coroutine failing with cancellation, then you successfully caught the failed dependent coroutine and can recover:<\/p>\n<pre>IAsyncOperation&lt;int&gt; GetWidgetCountAsync()\r\n{\r\n    try {\r\n        auto widgets = co_await GetAllWidgetsAsync();\r\n        co_return widgets.Count();\r\n    } catch (hresult_canceled const&amp;) {\r\n        co_return 0;\r\n    }\r\n}\r\n<\/pre>\n<p>If the <code>Get\u00adAll\u00adWidgets\u00adAsync<\/code> operation was cancelled (say, because it prompted the user for permission, and the user hit <i>Cancel<\/i>), then the <code>co_await GetAllWidgetsAsync()<\/code> will throw the cancellation exception, and you caught it and dealt with the problem by returning a widget count of zero.<\/p>\n<p>On the other hand, if the cancellation exception was generated because somebody cancelled <i>you<\/i>, then your coroutine has already been put into the cancelled state. It&#8217;s too late to <code>co_return<\/code> a value; the coroutine has already been cancelled. You can still do the <code>co_return<\/code>, but the value you return won&#8217;t be the result of the coroutine.<\/p>\n<p><b>Bonus chatter<\/b>: C++\/WinRT does not provide a way to rescue a coroutine from cancellation. You might think of adding a feature like <i>co_await uncancel_current_coroutine()<\/i> so that a coroutine could treat cancellation as a way to abandon an operation and generate partial results, but C++\/WinRT doesn&#8217;t do that. Once it&#8217;s cancelled, it&#8217;s cancelled.<\/p>\n<p>In theory, it could be possible to add &#8220;uncancellation&#8221;, but it would come at the expense of responsiveness: Right now, when an <code>IAsyncXxx<\/code> is cancelled, the operation immediately transitions to the cancelled state. The completion handler is called immediately, allowing the code that is awaiting the result of the coroutine to proceed without delay. If operations could be uncancelled, then the transition to the cancelled state would have to be delayed until the coroutine itself decided whether to accept the cancellation or to uncancel it.<\/p>\n<p>C++\/WinRT opted not to slow down the common case in order to add a feature that would rarely be used.<\/p>\n<p>\u00b9 The technical term for &#8220;making a statement for posterity&#8221; is <i>logging<\/i>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Some manual cleanup.<\/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-104005","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Some manual cleanup.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104005","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=104005"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104005\/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=104005"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104005"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104005"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}