{"id":104261,"date":"2020-09-23T07:00:00","date_gmt":"2020-09-23T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104261"},"modified":"2020-09-23T06:35:41","modified_gmt":"2020-09-23T13:35:41","slug":"20200923-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200923-00\/?p=104261","title":{"rendered":"How to get your C++\/WinRT asynchronous operations to respond more quickly to cancellation, part 3"},"content":{"rendered":"<p>Back in <a title=\"How to get your C++\/WinRT asynchronous operations to respond more quickly to cancellation, part 2\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200723-00\/?p=104000\"> How to get your C++\/WinRT asynchronous operations to respond more quickly to cancellation, part 2<\/a>, I introduced a helper function that propagates cancellation of a coroutine into the coroutines it is itself awaiting, so that the whole thing can cancel faster.<\/p>\n<p>The release of <a href=\"https:\/\/github.com\/microsoft\/cppwinrt\/releases\/tag\/2.0.200917.4\"> C++\/WinRT version 2.0.200917.4<\/a> includes a new feature: Automatic propagation of cancellation. You can read <a href=\"https:\/\/github.com\/microsoft\/cppwinrt\/pull\/721\"> the pull request<\/a> for a deep dive.<\/p>\n<p>The way it works for you, the application developer, is that you can call the <code>enable_propagation()<\/code> method on the cancellation token. If the coroutine is cancelled while it is busy <code>co_await<\/code>ing another coroutine, the cancellation of the coroutine is propagated into the awaited-for coroutine, thereby hastening the death of the outer coroutine.<\/p>\n<p>For example:<\/p>\n<pre>IAsyncAction DoHttpThingAsync()\r\n{\r\n    auto cancellation = co_await get_cancellation_token();\r\n    <span style=\"color: blue;\">cancellation.enable_propagation();<\/span>\r\n\r\n    auto result = co_await httpClient.TryGetStringAsync(uri);\r\n\r\n    if (result.Succeeded()) {\r\n        DoSomethingWith(result.Value());\r\n    }\r\n}\r\n<\/pre>\n<p>Thanks to the <code>enable_propagation()<\/code>, if <code>Do\u00adHttp\u00adThing\u00adAsync<\/code> is cancelled while awaiting the result of <code>Try\u00adGet\u00adString\u00adAsync<\/code>, the <code>Try\u00adGet\u00adString\u00adAsync<\/code> operation is cancelled immediately rather than waiting for it to complete on its own, thereby avoiding a very lengthy network timeout.<\/p>\n<p>The <code>enable_propagation()<\/code> method takes an optional <code>bool<\/code> parameter which specifies whether you want to enable cancellation propagation (<code>true<\/code>, the default value) or disable it (<code>false<\/code>). It also returns the previous setting, so you can restore it if you need to.<\/p>\n<p>Cancellation propagation is now built into C++\/WinRT, so we don&#8217;t need the <code>Make\u00adCancellable<\/code> helper we saw in Part 2.<\/p>\n<p>What&#8217;s more, cancellation propagation supports <code>resume_on_signal<\/code> and <code>resume_after<\/code>, so you can cancel out of a wait operation. This is important for <code>resume_on_signal<\/code>: If you&#8217;re cancelling the operation because the kernel object will never be signaled (e.g., because you realize that the thing you&#8217;re waiting for will never happen), you need some way to get the coroutine to resume (in a canceled state) so it can clean up its resources. Otherwise, the coroutine will get stuck in a permanently-suspended state and end up leaked.<\/p>\n<p>For compatibility with previous versions of C++\/WinRT, automatic cancellation propagation is disabled by default. You must opt in by calling <code>enable_propagation()<\/code>.<\/p>\n<p><b>Bonus chatter<\/b>: The implementation of cancellation propagation is a little odd because it is optimized for the case that cancellation never occurs. Most of the expensive work happens during cancellation, with only a tiny bit of bookkeeping performed at each <code>co_await<\/code>. I paid for this extra cost by removing a lock from the <code>co_await<\/code> path in <a href=\"https:\/\/github.com\/microsoft\/cppwinrt\/pull\/717\"> PR 171<\/a>.<\/p>\n<p><b>Bonus bonus chatter<\/b>: Any awaiter can participate in cancellation propagation by being convertible to <code>winrt::<wbr \/>enable_<wbr \/>await_<wbr \/>cancellation<\/code> (typically by inheriting from it) and implementing the <code>enable_<wbr \/>cancellation()<\/code> method. Here is the breakdown for C++\/WinRT awaitables:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th colspan=\"2\">Supports cancellation propagation<\/th>\n<\/tr>\n<tr>\n<th>Yes<\/th>\n<th>No<\/th>\n<\/tr>\n<tr>\n<td valign=\"top\"><code>IAsyncXxx<\/code> <br \/>\n<code>resume_after<\/code> <br \/>\n<code>resume_on_signal<\/code><\/td>\n<td valign=\"top\"><code>apartment_context<\/code> <br \/>\n<code>resume_background<\/code> <br \/>\n<code>resume_foreground<\/code> <br \/>\n<code>thread_pool<\/code> <br \/>\n<code>wait_for_deferrals<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The thread-switching awaitables do not support cancellation propagation because there&#8217;s nowhere to cancel back to. The original thread is long gone. The only way to proceed is to go forward and resume on the target thread.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Automatic cancellation propagation is here.<\/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-104261","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Automatic cancellation propagation is here.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104261","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=104261"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104261\/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=104261"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104261"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104261"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}