{"id":88,"date":"2020-04-28T13:33:56","date_gmt":"2020-04-28T20:33:56","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sdk\/?p=88"},"modified":"2020-06-12T15:05:05","modified_gmt":"2020-06-12T22:05:05","slug":"how-to-use-cancellationtokens-to-cancel-tasks-in-the-azure-sdk-for-net","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sdk\/how-to-use-cancellationtokens-to-cancel-tasks-in-the-azure-sdk-for-net\/","title":{"rendered":"How to use CancellationTokens to cancel tasks in the Azure SDK for .NET"},"content":{"rendered":"<p>The ability to cancel long-running tasks is important to help keep applications responsive. Whether the network connection is slow or disconnects, or the user just wants to cancel a long task, using a <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.cancellationtoken\"><code>CancellationToken<\/code><\/a> in .NET makes it easy to cancel those long tasks. Together with a <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.cancellationtokensource\"><code>CancellationTokenSource<\/code><\/a>, a developer can provide on-demand or timed cancellations of tasks that accept a <code>CancellationToken<\/code>, like our <a href=\"https:\/\/azure.github.io\/azure-sdk\/general_design.html#network-requests\">client methods<\/a> in the Azure SDK for .NET.<\/p>\n<h2>Using CancellationTokens<\/h2>\n<p>Prior to the introduction of the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.cancellationtoken\"><code>CancellationToken<\/code><\/a> structure in .NET Framework 4.0, it was common to use one or more <code>WaitHandle<\/code> objects to synchronize threads. This same pattern has been used in native Windows applications for decades. When the asynchronous task pattern was introduced in .NET, a new, simpler pattern for cancelling tasks was also introduced. While a <code>CancellationToken<\/code> can still provide a <code>WaitHandle<\/code> to synchronize threads, creating tokens and passing them to methods is much easier:<\/p>\n<pre><code>CancellationTokenSource cts = new CancellationTokenSource();\nKeyVaultSecret secret = await secretClient.GetSecretAsync(\"my-secret\", cts.Token);\n<\/code><\/pre>\n<p><code>CancellationTokenSource.Token<\/code> returns a <code>CancellationToken<\/code> that can be passed to other methods further down the call stack, or even on other threads. When those tokens are canceled, any methods waiting on them should throw an <code>OperationCanceledException<\/code>. Methods accepting a <code>CancellationToken<\/code> don&#8217;t even have to be asynchronous. Our synchronous client methods in the Azure SDK for .NET also accept a <code>CancellationToken<\/code>. In both synchronous or asynchronous code, you can simply call <code>CancellationToken.ThrowIfCancellationRequested()<\/code> to immediately throw if the token was in a canceled state.<\/p>\n<pre><code>public void DoWork(CancellationToken cancellationToken = default)\n{\n    cancellationToken.ThrowIfCancellationRequested();\n\n    \/\/ Start long-running task...\n}\n<\/code><\/pre>\n<p>Notice that we didn&#8217;t have to check if <code>cancellationToken<\/code> was null. As a value type in .NET, it cannot be null and even defined as its default value, <code>CancellationToken.ThrowIfCancellationRequested()<\/code> will simply do nothing.<\/p>\n<p>If you don&#8217;t want to throw an exception but still want to check if a token was cancelled, you can check the <code>CancellationToken.IsCancellationRequested<\/code> property.<\/p>\n<h2>Cancelling CancellationTokens<\/h2>\n<p>You&#8217;ve seen a few ways to pass and use a <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.cancellationtoken\"><code>CancellationToken<\/code><\/a>, but how do you actually cancel them? That ability is supported by the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.cancellationtokensource\"><code>CancellationTokenSource<\/code><\/a>. A <code>CancellationTokenSource<\/code> can cancel tokens on demand or after a certain amount of time:<\/p>\n<pre><code>CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));\nConsole.CancelKeyPress += (source, args) =&gt;\n{\n    Console.Error.WriteLine(\"Cancelling download...\");\n\n    args.Cancel = true;\n    cts.Cancel();\n};\n\nConsole.WriteLine(\"Downloading to {0}...\", path);\n\ntry\n{\n    using (Stream fs = File.Create(path))\n    await blobClient.DownloadToAsync(fs, cts.Token);\n}\ncatch (OperationCanceledException)\n{\n    Console.Error.WriteLine(\"Download canceled. Deleting {0}...\", path);\n    File.Delete(path);\n}\n<\/code><\/pre>\n<p>We created a <code>CancellationTokenSource<\/code> that will cancel all its tokens after 30 seconds, and also hooked up a handler for pressing <strong>Ctrl+C<\/strong> in this sample console application. This way, we provide flexibility to the user to cancel the task whenever they want, and also cancel the task if it takes too long, which might indicate a network error if a suitable timeout is chosen. If the download is cancelled, we can handle the <code>OperationCanceledException<\/code> to delete the file in case it was partially downloaded.<\/p>\n<h2>Cancelling Long-running Operations in Azure SDK<\/h2>\n<p>A subtle but important distinction is that long-running operations (LROs) in Azure SDK often refer to specific classes like <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.security.keyvault.certificates.certificateoperation\"><code>CertificateOperation<\/code><\/a>. After methods that return LROs like <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.security.keyvault.certificates.certificateclient.startcreatecertificateasync\"><code>StartCreateCertificateAsync<\/code><\/a> have completed, canceling a <code>CancellationToken<\/code> passed to any methods like <code>CertificateOption.UpdateStatusAsync<\/code> only cancels waiting on that method. To cancel an LRO, you need to call <code>Cancel<\/code> or <code>CancelAsync<\/code> on the LRO to actually cancel the operation.<\/p>\n<pre><code>CertificateOperation op = await certificateClient.StartCreateCertificateAsync(\"my-certificate\", certificatePolicy);\n\n\/\/ After some time, user decides to cancel certificate creation.\nawait op.CancelAsync();\n<\/code><\/pre>\n<p>These long-running operations (LROs) can often take longer than most programs are expected to run. Creating a certificate, for example, could take days of approvals. These LROs can be recreated &#8211; like in this example, using the pending certificate name &#8211; and code can continue to wait or cancel them later.<\/p>\n<h2>Advanced uses<\/h2>\n<p>Despite its simple design, a <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.cancellationtoken\"><code>CancellationToken<\/code><\/a> can be used in a number of other scenarios.<\/p>\n<h3>Doing additional work when canceled<\/h3>\n<p>Canceling any current or pending work is a typical scenario when a <code>CancellationToken<\/code> is canceled. In some scenarios, you might need to do some clean up only when canceled. For this reason, you can register a delegate to run only when the <code>CancellationToken<\/code> is canceled. Registering a delegate returns an <code>IDisposable<\/code> which can be disposed to unregister the delegate if no longer needed. We could log a message, for example, that a method was canceled without worrying where in code to write the message:<\/p>\n<pre><code>public async Task DoWorkAsync(CancellationToken cancellationToken = default)\n{\n    using (CancellationTokenRegistration  cts = cancellationToken.Register(() =&gt;\n    {\n        Console.Error.WriteLine(\"The task was cancelled.\");\n    }))\n    {\n        \/\/ Start long-running task...\n    }\n}\n<\/code><\/pre>\n<h3>Linking CancellationTokens<\/h3>\n<p>There may be times when you have a group of tasks you want to cancel individually or all together, for example downloading as many files as possible and reporting on those that failed. You can link a <code>CancellationToken<\/code> to a new <code>CancellationTokenSource<\/code> so that when that original <code>CancellationToken<\/code> is canceled, any tokens created from a linked <code>CancellationTokenSource<\/code> are canceled:<\/p>\n<pre><code>public async Task DownloadAsync(Uri uri, CancellationToken cancellationToken = default)\n{\n    using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))\n    {\n        cts.CancelAfter(TimeSpan.FromSeconds(30));\n\n        \/\/ Download file ...\n    }\n}\n<\/code><\/pre>\n<p>It&#8217;s important to note that linked <code>CancellationToken<\/code>s only work in one direction: canceling a <code>CancellationToken<\/code> from a linked <code>CancellationTokenSource<\/code> does not cancel the original <code>CancellationToken<\/code>.<\/p>\n<h3>Waiting on handles<\/h3>\n<p>You can also use a <code>CancellationToken<\/code> with code that requires waiting on one or more <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.waithandle\"><code>WaitHandle<\/code><\/a> objects. This may be common for older applications or when interoperating with native code like with P\/Invoke. The <code>CancellationToken.WaitHandle<\/code> can be used in calls like <code>WaitHandle.WaitAny<\/code>:<\/p>\n<pre><code>AutoResetEvent evt = new AutoResetEvent(false);\nThreadPool.QueueUserWorkItem(state =&gt; DoWork(state, evt));\n\nint which = WaitHandle.WaitAny(new WaitHandle[] { evt, cancellationToken.WaitHandle});\nif (which == 1)\n{\n    Console.Error.WriteLine(\"The task was canceled.\");\n}\n<\/code><\/pre>\n<p>Updating APIs for older applications to use newer classes like <code>CancellationToken<\/code> may help when upgrading to newer components.<\/p>\n<h2>Further reading<\/h2>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.cancellationtoken\"><code>CancellationToken<\/code><\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.cancellationtokensource\"><code>CancellationTokenSource<\/code><\/a><\/li>\n<li><a href=\"https:\/\/azure.github.io\/azure-sdk\/general_design.html#network-requests\">Azure SDK guidelines for network requests<\/a>.<\/li>\n<\/ul>\n<h2>Want to hear more?<\/h2>\n<p>Follow us on Twitter at <a href=\"https:\/\/twitter.com\/AzureSDK\">@AzureSDK<\/a>. We&#8217;ll be covering more best practices in cloud-native development as well as providing updates on our progress in developing the next generation of Azure SDKs.<\/p>\n<p>Contributors to this article: <em><a href=\"https:\/\/twitter.com\/mrhestew\">Heath Stewart<\/a><\/em>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The ability to cancel long-running tasks is important to help keep applications responsive. Whether the network connection is slow or disconnects, or the user just wants to cancel a long task, using a [`CancellationToken`][CancellationToken] in .NET makes it easy to cancel those long tasks.<\/p>\n","protected":false},"author":389,"featured_media":99,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[161],"class_list":["post-88","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-sdk","tag-dotnet"],"acf":[],"blog_post_summary":"<p>The ability to cancel long-running tasks is important to help keep applications responsive. Whether the network connection is slow or disconnects, or the user just wants to cancel a long task, using a [`CancellationToken`][CancellationToken] in .NET makes it easy to cancel those long tasks.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/88","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/users\/389"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/comments?post=88"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/88\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media\/99"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media?parent=88"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/categories?post=88"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/tags?post=88"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}