{"id":103923,"date":"2020-07-02T07:00:00","date_gmt":"2020-07-02T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103923"},"modified":"2020-07-02T06:47:28","modified_gmt":"2020-07-02T13:47:28","slug":"20200702-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200702-00\/?p=103923","title":{"rendered":"Cancelling a Windows Runtime asynchronous operation, part 2: C++\/CX with PPL, explicit continuation style"},"content":{"rendered":"<p>We began our investigation of Windows Runtime cancellation with <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200701-00\/?p=103916\"> how task cancellation is projected in C#<\/a>. But how about C++\/CX with PPL and explicit continuations?<\/p>\n<p>Okay, let&#8217;s do this.<\/p>\n<pre>auto picker = ref new FileOpenPicker();\r\npicker-&gt;FileTypeFilter.Append(L\".txt\");\r\n\r\n<span style=\"color: blue;\">cancellation_token_source cts;\r\nauto do_cancel = std::make_shared&lt;call&lt;bool&gt;&gt;([cts](bool) { cts.cancel(); });\r\nauto delayed_cancel = std::make_shared&lt;timer&lt;bool&gt;&gt;(3000U, false, do_cancel.get());\r\ndelayed_cancel-&gt;start();<\/span>\r\n\r\ncreate_task(picker-&gt;PickSingleFileAsync()).\r\n    then([do_cancel, delayed_cancel](task&lt;StorageFile^&gt; precedingTask)\r\n    {\r\n        StorageFile^ file;\r\n        try {\r\n            file = precedingTask.get();\r\n        } <span style=\"color: blue;\">catch (task_canceled const&amp;)<\/span> {\r\n            file = nullptr;\r\n        }\r\n\r\n        if (file != nullptr) {\r\n            DoSomething(file);\r\n        }\r\n    });\r\n<\/pre>\n<p>Setting up the timer to cancel the task is quite annoying. Both <code>call<\/code> objects and <code>timer<\/code> objects are non-copyable, but we need to keep both of the objects alive for the duration of the asynchronous operation, so we need to copy them into the lambda so that they will not be destructed prematurely. But then you run into that whole &#8220;non-copyable&#8221; business.<\/p>\n<p>Your next thought would be to initialize the objects directly into the lambda:<\/p>\n<pre>    then([<span style=\"color: blue;\">do_cancel = call&lt;bool&gt;(...),\r\n          delayed_cancel = timer&lt;bool&gt;(...)<\/span>]\r\n         (task&lt;StorageFile^&gt; precedingTask)\r\n<\/pre>\n<p>But that too doesn&#8217;t work because the lambda is copied around internally by PPL, so we once again run into the &#8220;non-copyable&#8221; problem.<\/p>\n<p>We address the problem by putting both the <code>call<\/code> and the <code>timer<\/code> in a <code>shared_ptr<\/code>. The <code>shared_ptr<\/code> is copyable, and when the last one destructs, <code>call<\/code> and <code>timer<\/code> are destroyed.<\/p>\n<p>Okay, that was a long and annoying aside.<\/p>\n<p>When the underlying Windows Runtime asynchronous operation completes, PPL propagates the status into the task. You can see this happen in <code>ppltasks.h<\/code>. (I&#8217;ve simplified the code a bit for expository purposes.)<\/p>\n<pre>_AsyncOp-&gt;Completed = ref new AsyncOperationCompletedHandler&lt;_ReturnType&gt;(\r\n              [_OuterTask](auto^ _Operation, AsyncStatus _Status) mutable\r\n{\r\n    if (_Status == AsyncStatus::Canceled)\r\n    {\r\n        <span style=\"color: blue;\">_OuterTask-&gt;_Cancel(true);<\/span>\r\n    }\r\n    else if (_Status == AsyncStatus::Error)\r\n    {\r\n        _OuterTask-&gt;_CancelWithException(\r\n            std::make_exception_ptr(::ReCreateException(_Operation-&gt;ErrorCode.Value)));\r\n    }\r\n    else\r\n    {\r\n        _ASSERTE(_Status == AsyncStatus::Completed);\r\n\r\n         try\r\n        {\r\n            _OuterTask-&gt;_FinalizeAndRunContinuations(_Operation-&gt;GetResults());\r\n        }\r\n        catch (...)\r\n        {\r\n            \/\/ unknown exceptions thrown from GetResult\r\n            _OuterTask-&gt;_CancelWithException(std::current_exception());\r\n        }\r\n}\r\n<\/pre>\n<p>When the operation completes, PPL looks at the status code. If the status code says that the operation was canceled, then it cancels the wrapper task. If it says that the operation encountered an error, then it synthesizes an exception object from the error code and puts it in the wrapper task. Otherwise, the operation succeeded, so we get the results from the operation (<code>_Operation-&gt;GetResults()<\/code>) and set that as the result of the wrapper task. (There&#8217;s an extra wrinkle: If <code>GetResults<\/code> itself throws an exception, then the wrapper task is set into an error state with that exception.)<\/p>\n<p>Okay, so that&#8217;s how the cancellation gets <i>into<\/i> the wrapper task. How does it come out?<\/p>\n<p>PPL throws a <code>task_canceled<\/code> object when you try to get the results of a canceled task. This is <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/parallel\/concrt\/reference\/task-class?view=vs-2019#get\"> documented under <code>task.get()<\/code><\/a>, and you can see it happen in <code>ppltask.h<\/code>:<\/p>\n<pre>_ReturnType get() const\r\n{\r\n    if (!_M_Impl)\r\n    {\r\n        details::_DefaultTaskHelper::_NoCallOnDefaultTask_ErrorImpl();\r\n    }\r\n\r\n    if (_M_Impl-&gt;_Wait() == canceled)\r\n    {\r\n        <span style=\"color: blue;\">_THROW(task_canceled{});<\/span>\r\n    }\r\n\r\n    return _M_Impl-&gt;_GetResult();\r\n}\r\n<\/pre>\n<p>Next time, we&#8217;ll look at PPL with coroutines.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Going through a different wrapper.<\/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-103923","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Going through a different wrapper.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103923","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=103923"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103923\/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=103923"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103923"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103923"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}