Today asynchronous programming is everywhere. It’s hard to find software which doesn’t perform asynchronous operations. Many async operations can take a long time to finish. Sometimes they can last infinitely. This situation dictates the necessity to have a possibility to cancel operations. And this is the topic we are going to talk about: canceling operations.
When I was a newcomer I faced with a chunk of long-running code that didn’t support cancellation. I needed to cancel it somehow. Unfortunately, I had no access to that code, so I couldn’t modify it. I was struggling with the requirement to be able to cancel that code. I was thinking about the problem for two days as I remember and I couldn’t believe that it’s actually not possible to cancel random code. Yes, there is the Thread.Abort method presented since .NET 1, but it’s strongly not recommended to use it, because we can’t predict what will happen to the code which is going to be aborted. It’s even almost (or absolutely) impossible to write code which is reliable in case it is aborted by Thread.Abort, because it’s even not guaranteed that your finally-blocks will be executed. One will say that I’m talking about self-evident things, but I’ve heard many times from developers exclamations like, “why we can’t simply use Thread.Abort in order to interrupt that function?” That’s why I decided to write this post, showing how simple it is to write cancellable code.
Cooperative cancellation
The only correct way to cancel code we have in .NET relies on the concept of the cooperative cancellation. Why cooperative cancellation? Because almost always both sides should be aware of that cancellation happened. Client-side should be aware of that the code was actually canceled, it’s not enough for a client to know that cancellation was just requested. For the callee, it’s important to know about the fact that cancellation was requested in order to properly finish itself. In short, all participants of cancellation should cooperate with each other. Sometimes participants can reside on different layers, but despite that, they still should cooperate somehow with each other.
When canceling is dangerous
Unfortunately, our world is not perfect as well as computers or any other machines. Some processes in the real world can’t be canceled, so there always will be examples of operations which can’t be canceled by its nature. Sometimes we write drivers for devices with imperfectly implemented firmware and when we try to discard bytes have been sent to the port of the device, that device’s firmware hangs up infinitely. Sometimes we can do a little bit slick thing, we can pretend that the operation was canceled, allowing a user to go somewhere to do other things, but not allowing him to repeat the operation previously requested (which actually is still running). In this case, we should internally control the requested operation in order to properly handle user input, thus preserving a correct state of the program at the same time.
C# task cancellation
In order to cancel an operation (stop a task / cancel task) we should use CancellationTokenSource and CancellationTokenSource.Token (so-called task cancellation token) classes, where Token is a property of CancellationTokenSource. The sample would look like:
[code language=”csharp”]
void StartLongRunningOperation() {
var s = new CancellationTokenSource();
var task = LongRunningOperation(s.Token);
s.Cancel();
try {
await task;
}
catch (OperationCanceledException) {}
}
async Task LongRunningOperation(CancellationToken t) {
while (ShouldProcceed) {
t.ThrowIfCancellationRequested();
//do some work
Thread.Sleep(1000);
}
}[/code]
The caller starts an operation which takes CancellationToken as a parameter. Callee uses ThrowIfCancellationRequested in order to throw OperationCanceledException, thus interrupting the current operation. If a caller wants to be aware of the actual ending of the operation then this is the best way. Sometimes it can be useful to check IsCancellationRequested property:
[code language=”csharp”]
async Task LongRunningOperation(CancellationToken t) {
while (ShouldProcceed) {
t.ThrowIfCancellationRequested();
//do some work
Thread.Sleep(1000);
}
}[/code]
If the caller doesn’t want to pass a Token, then CancellationToken.None comes to the rescue. CancellationToken.None is a special case of Token which can’t be even canceled.
It’s a common case when we want to invoke Cancel by the timeout. This feature is already implemented in .NET 4.5 by the CancelAfter method:
[code language=”csharp”]
async void StartLongRunningOperation(object sender, RoutedEventArgs e) {
var s = new CancellationTokenSource();
var task = LongRunningOperation(s.Token);
s.CancelAfter(TimeSpan.FromSeconds(3));
try {
await task;
}
catch (OperationCanceledException) {}
}[/code]
In case you’re tied to .NET 4.0, you can implement it easily by yourself.
Another cool, useful feature about which most developers are unaware of is Combining Tokens. This feature comes to the scene when the layer requiring a Cancellation Token in one of its methods and at the same time one of its operations being invoked should cancel that method. Example of tokens combining you can find here.
Register for cancellation
Another cool feature of CancellationToken is the ability to Register a callback for the Cancel event. It’s commonly used for IO bound operation or operations which are not CPU-bound at all:
[code language=”csharp”]
WebClient wc = new WebClient();
private async Task LongRunningOperation(CancellationToken t) {
if (!t.IsCancellationRequested) {
// Register the callback to a method that can unblock.
using (CancellationTokenRegistration ctr = t.Register(() => {
//cancel I/O bound operation, which doesn’t support cancellation tokens
wc.CancelAsync();
})) {
//interruptable I/O bound operation
wc.DownloadStringAsync(…));
}
}
}[/code]
Or you can implement cancellable await like in the following example using Register:
[code language=”csharp”]
public static async Task WithCancellation(this Task task, CancellationToken cancellationToken) {
var tcs = new TaskCompletionSource();
using (cancellationToken.Register(
s => ((TaskCompletionSource)s).TrySetResult(true), tcs))
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
return await task;
}[/code]
Next time we will look at how to implement ProgressBar with cancellation and with the support of unit-testing.
I have a video course “Multithreading and Parallel Programming in C#”.
You, as a reader of my blog, are eligible for taking it with a max possible discount.