{"id":103916,"date":"2020-07-01T07:00:00","date_gmt":"2020-07-01T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103916"},"modified":"2020-07-01T15:09:25","modified_gmt":"2020-07-01T22:09:25","slug":"20200701-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200701-00\/?p=103916","title":{"rendered":"Cancelling a Windows Runtime asynchronous operation, part 1: C#"},"content":{"rendered":"<p>The Windows Runtime has a lot of asynchronous operations, and the typical way of dealing with them is to schedule a continuation when the operation completes. Depending on the language, it might be <code>Promise.then<\/code> or <code>task.then()<\/code> or <code>await<\/code> or <code>co_await<\/code>. They all boil down to &#8220;Return from the function immediately, and resume execution when this asynchronous thing produces a value.&#8221;<\/p>\n<p>Windows Runtime asynchronous operations can be cancelled, and then things get interesting.<\/p>\n<p>Invoking the <code>Cancel<\/code> method is, technically, merely a request and not a demand. Operations are advised to support cancellation, but it is not strictly required. An operation might choose to ignore your cancellation request outright. Or it could process the cancellation request by causing the operation to complete immediately with partial results.<\/p>\n<p>But let&#8217;s assume that the operation accepted your cancel request and indeed completed the operation with a status of <code>Canceled<\/code>.<\/p>\n<p>What does your code see as the result of the cancellation?<\/p>\n<p>Let&#8217;s start with C#. In order to cancel the operation, you have to wrap it in a task, and then cancel the task.<\/p>\n<pre>var picker = new FileOpenPicker { FileTypeFilter = { \".txt\" } };\r\n<span style=\"color: blue;\">var cts = new CancellationTokenSource();\r\ncts.CancelAfter(TimeSpan.FromSeconds(3));<\/span>\r\n\r\nStorageFile file;\r\ntry {\r\n    file = await picker.PickSingleFileAsync()<span style=\"color: blue;\">.AsTask(cts.Token)<\/span>;\r\n} <span style=\"color: blue;\">catch (TaskCanceledException)<\/span> {\r\n    file = null;\r\n}\r\n\r\nif (file != null) {\r\n    DoSomething(file);\r\n}\r\n<\/pre>\n<p>We cancel the file picker dialog after three seconds. This is done by taking the <code>IAsyncOperation<\/code> returned by <code>Pick\u00adSingle\u00adFile\u00adAsync()<\/code>, convert it to a <code>Task<\/code> with <code>AsTask<\/code>, and associate it with a cancellation token source that has been configured to cancel after three seconds.<\/p>\n<p>When this operation is canceled, you get a <code>Task\u00adCanceled\u00adException<\/code>. This was the result of the Windows Runtime asynchronous operation completing with a status of <code>Canceled<\/code>. The C# projection then converts this to a <code>Task\u00adCanceled\u00adException<\/code>.<\/p>\n<p>This is the behavior that C# asynchronous code expects, so it&#8217;s natural that the C# projection of Windows Runtime asynchronous operations into tasks behaves this way.<\/p>\n<p>Next time, we&#8217;ll look at C++\/CX with PPL.<\/p>\n<p><b>Bonus chatter<\/b>: You can also cancel the task by talking directly to the <code>IAsyncOperation<\/code> instead of converting it into a C# Task.<\/p>\n<pre>async void CancelAfter(IAsyncInfo info, TimeSpan delay)\r\n{\r\n    await Task.delay(delay);\r\n    info.Cancel();\r\n}\r\n\r\nvar picker = new FileOpenPicker { FileTypeFilter = { \".txt\" } };\r\nStorageFile file;\r\ntry {\r\n    <span style=\"color: blue;\">var op = picker.PickSingleFileAsync();\r\n    CancelAfter(op, TimeSpan.FromSeconds(3));\r\n    file = await op<\/span>;\r\n} <span style=\"color: blue;\">catch (TaskCanceledException)<\/span> {\r\n    file = null;\r\n}\r\n\r\nif (file != null) {\r\n    DoSomething(file);\r\n}\r\n<\/pre>\n<p>You could try to earn some style points by moving the <code>CancelAfter<\/code> code inline.<\/p>\n<pre>var picker = new FileOpenPicker { FileTypeFilter = { \".txt\" } };\r\nStorageFile file;\r\ntry {\r\n    var op = picker.PickSingleFileAsync();\r\n    <span style=\"color: blue;\">((Action)(async () =&gt; { await Task.Delay(TimeSpan.FromSeconds(3)); op.Cancel(); }))();<\/span>\r\n    file = await op;\r\n} catch (TaskCanceledException) {\r\n    file = null;\r\n}\r\n<\/pre>\n<p>Or perhaps more usefully, let <code>CancelAfter<\/code> return the original asynchronous operation, so you can cancel it and await it at one go.<\/p>\n<pre>public static class Helpers\r\n{\r\n    static async void CancelAfterHelper(IAsyncInfo info, TimeSpan delay)\r\n    {\r\n        await Task.Delay(delay);\r\n        info.Cancel();\r\n    }\r\n\r\n    static public IAsyncAction CancelAfter(this IAsyncAction action, TimeSpan delay)\r\n    {\r\n        CancelAfterHelper(action, delay);\r\n        return action;\r\n    }\r\n\r\n    static public IAsyncOperation&lt;T&gt;\r\n        CancelAfter&lt;T&gt;(this IAsyncOperation&lt;T&gt; op, TimeSpan delay)\r\n    {\r\n        CancelAfterHelper(op, delay);\r\n        return op;\r\n    }\r\n}\r\n\r\nvar picker = new FileOpenPicker { FileTypeFilter = { \".txt\" } };\r\nStorageFile file;\r\ntry {\r\n    file = await picker.PickSingleFileAsync()<span style=\"color: blue;\">.CancelAfter(TimeSpan.FromSeconds(3))<\/span>;\r\n} catch (TaskCanceledException) {\r\n    file = null;\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>How is the cancellation reported?<\/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-103916","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>How is the cancellation reported?<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103916","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=103916"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103916\/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=103916"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103916"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103916"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}