{"id":36047,"date":"2014-03-19T14:26:00","date_gmt":"2014-03-19T14:26:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/andrewarnottms\/2014\/03\/19\/recommended-patterns-for-cancellationtoken\/"},"modified":"2021-04-30T07:02:19","modified_gmt":"2021-04-30T14:02:19","slug":"recommended-patterns-for-cancellationtoken","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/premier-developer\/recommended-patterns-for-cancellationtoken\/","title":{"rendered":"Recommended patterns for CancellationToken"},"content":{"rendered":"<p>Whether you&#8217;re doing async work or not, accepting a CancellationToken as a parameter to your method is a great pattern for allowing your caller to express lost interest in the result.<\/p>\n<p>Supporting cancelable operations comes with a little bit of extra responsibility on your part.<\/p>\n<ol>\n<li><strong>Know when you&#8217;ve passed the point of no cancellation.<\/strong><em>\u00a0Don\u2019t<\/em> cancel if you\u2019ve already incurred side-effects that your method isn\u2019t prepared to revert on the way out that would leave you in an inconsistent state. So if you\u2019ve done some work, and have a lot more to do, and the token is cancelled, you must only cancel when and if you can do so leaving\u00a0objects in a valid state.\u00a0This may mean that you have to finish the large amount of work, or undo all your previous work (i.e. revert the side-effects), or find a convenient place that you can stop halfway through but in a valid condition,\u00a0before then throwing OperationCanceledException. In other words, the caller must be able to recover to a known consistent state after cancelling your work, or realize that cancellation was not responded to and that the caller then must decide whether to accept the work, or revert its successful completion on its own.<\/li>\n<li><strong>Propagate your CancellationToken<\/strong> to all the methods you call that accept one, <em>except<\/em> after the &#8220;point of no cancellation&#8221; referred to in the previous point. In fact if your method mostly orchestrates calls to other methods that themselves take CancellationTokens, you may find that you don&#8217;t personally have to call CancellationToken.ThrowIfCancellationRequested() at all, since the async methods you&#8217;re calling will generally do it for you.<\/li>\n<li><strong>Don\u2019t\u00a0throw OperationCanceledException\u00a0after you&#8217;ve completed the work,<\/strong> just because the token was signaled. Return a successful result and let the caller decide what to do next. The caller can\u2019t assume you\u2019re cancellable at a given point anyway so they have to be prepared for a successful result even upon cancellation.<\/li>\n<li><strong>Input validation<\/strong> can certainly go ahead of cancellation checks (since that helps highlight bugs in the calling code).<\/li>\n<li><strong>Consider not checking the token at all<\/strong> if your work is very quick, or you propagate it to the methods you call. That said, calling CancellationToken.ThrowIfCancellationRequested() is pretty lightweight so don&#8217;t think too hard about this one unless you see it on perf traces.<\/li>\n<li><strong>Check CancellationToken.CanBeCanceled<\/strong> when you can do your work more efficiently if you can assume you&#8217;ll never be canceled. CanBeCanceled returns false for CancellationToken.None, and in the future possibly for other cases as well.<\/li>\n<\/ol>\n<h3>Optional\u00a0CancellationToken parameter<\/h3>\n<p>If you want to accept CancellationToken but want to make it optional, you can do so with syntax such as this:<\/p>\n<pre class=\"\">public Task SomethingExpensiveAsync(CancellationToken cancellationToken = default(CancellationToken))\r\n{\r\n  \/\/ don't worry about NullReferenceException if the\r\n  \/\/\u00a0caller omitted the argument because it's a struct.\r\n\u00a0 cancellationToken.ThrowIfCancellationRequested();\r\n}<\/pre>\n<p><span style=\"font-family: arial,helvetica,sans-serif;\">Or equivalent in VB:<\/span><\/p>\n<pre>Function SomethingExpensiveAsync(Optional cancellationToken As CancellationToken = Nothing) As Task\r\n  cancellationToken.ThrowIfCancellationRequested()\r\nEnd Function<\/pre>\n<p>It&#8217;s a good idea to only make your CancellationToken parameters optional in your public API (if you have one) and leave them as required parameters everywhere else. This really helps to ensure that you intentionally propagate your CancellationTokens through all the methods you call (#2 above). But of course remember to switch to passing CancellationToken.None once you pass the point of no cancellation.<\/p>\n<p>It&#8217;s also a good API pattern to keep your CancellationToken as the <em>last<\/em>\u00a0parameter your method accepts. This fits nicely with optional parameters anyway since they have to show up after any required parameters.<\/p>\n<h3>Handling cancellation exceptions<\/h3>\n<p><span style=\"font-family: courier new,courier; font-size: small;\"><span style=\"font-family: Times New Roman,serif;\"><span style=\"font-family: Courier New;\"><span lang=\"en\"><span style=\"font-family: arial,helvetica,sans-serif;\">If you&#8217;ve experienced cancellation before, you&#8217;ve probably noticed a couple of types of these exceptions: TaskCanceledException and OperationCanceledException. TaskCanceledException <em>derives<\/em> from OperationCanceledException. That means when writing your catch blocks that deal with the fallout of a canceled operation, you should catch OperationCanceledException. If you catch TaskCanceledException you may let certain cancellation occurrences slip through your catch blocks (and possibly crash your app).<\/span><\/span><\/span><\/span><\/span><\/p>\n<pre>async Task UserSubmitClickAsync(CancellationToken cancellationToken)\r\n{\r\n  try\r\n  {\r\n    await SendResultAsync(cancellationToken);\r\n  }\r\n  catch (OperationCanceledException ex) \/\/ includes TaskCanceledException\r\n  {\r\n    MessageBox.Show(\"Your submission was canceled.\");\r\n  }\r\n}<\/pre>\n<p>If your cancelable method is in between other cancelable operations, you may need to perform clean up when canceled. When doing so, you can use the above catch block, but be sure to rethrow properly:<\/p>\n<pre class=\"\">async Task SendResultAsync(CancellationToken cancellationToken)\r\n{\r\n  try\r\n  {\r\n    await httpClient.SendAsync(form, cancellationToken);\r\n  }\r\n  catch (OperationCanceledException ex)\r\n  {\r\n    \/\/ perform your cleanup\r\n    form.Dispose();\r\n\r\n    \/\/ rethrow exception so caller knows you've canceled.\r\n    \/\/ DON'T \"throw ex;\" because that stomps on\r\n    \/\/ the Exception.StackTrace property.\r\n    throw;\r\n \u00a0}\r\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Whether you&#8217;re doing async work or not, accepting a CancellationToken as a parameter to your method is a great pattern for allowing your caller to express lost interest in the result. Supporting cancelable operations comes with a little bit of extra responsibility on your part. Know when you&#8217;ve passed the point of no cancellation.\u00a0Don\u2019t cancel [&hellip;]<\/p>\n","protected":false},"author":2685,"featured_media":37840,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[4617],"class_list":["post-36047","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-permierdev","tag-andarno"],"acf":[],"blog_post_summary":"<p>Whether you&#8217;re doing async work or not, accepting a CancellationToken as a parameter to your method is a great pattern for allowing your caller to express lost interest in the result. Supporting cancelable operations comes with a little bit of extra responsibility on your part. Know when you&#8217;ve passed the point of no cancellation.\u00a0Don\u2019t cancel [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/36047","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/users\/2685"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/comments?post=36047"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/36047\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media\/37840"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media?parent=36047"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/categories?post=36047"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/tags?post=36047"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}