It’s a practice to provide a Cancel button to a ‘tired of waiting’ user for a long running operation. In WinForms, you would probably create a BackgroundWorker or a separate thread to keep the UI responsive to the user can click the Cancel button. When the user clicks the Cancel button, how you actually implement the cancellation logic is the topic of this blog.
Please read Interrupt Politely, to get a complete overview of the cancellation options. Now, let’s get in to the types introduced with .Net 4.0 and what TPL has to offer.
The CalculateSum method below is our long running operation, that does nothing but adds a couple of numbers; keeps us posted on the calculated value via Console.WriteLine and sleeps on a condition.
The method CallerWithoutCancellation calls the above CalculateSum method via a Task.
Imagine, if the user wanted to cancel this long running task, there is no provision for cancellation that the method CalculateSum provides. If you ran CalculateSum with a classic Thread as opposed to a Task, then you would either issue a Thread.Abort or Thread.Interrupt. This is a decision you have to make carefully. Option 1 in the drdobbs journal and Aborting and Interrupting Threads talk more about them.
But with Task Parallel Library, CancellationTokenSource and CancellationToken, we have better options to cancel.
Let’s look at the implementation of the CalculateSum method with a new overload taking a CancellationToken parameter named inputCancellationToken. Pay careful attention to the line inputCancellationToken.ThrowIfCancellationRequested().
The caller, CallerWithCancellationToken, has a cancellation option on press of the Enter key; then it calls cancellationTokenSource.Cancel(). Also pay attention to the second parameter cancellationToken in the Task.Factory.StartNew method.
The CalculateSum method provides a provision for cancellation using the CancellationToken and the CallerWithCancellation method requests for a cancellation via the same CancellationToken.
As an alternative, you could also poll on the IsCancellationRequested property and proceed with clean up and cancellation.
What you effectively have here is a mechanism where the caller signals for a cancellation request via cancellationTokenSource.Cancel(), and the callee responds to it by cancelling itself via inputCancellationToken.ThrowIfCancellationRequested() or performing necessary clean up operations by checking inputCancellationToken.IsCancellationRequested.
Now, you see a coordination, an agreement between the caller and the callee, or in other words, they both cooperated for cancellation. And that’s why it’s called Cooperative Cancellation.
Download and play the source code here.
Note: This pattern existed way before TPL, but you had to rely on synchronization mechanism when reading or writing the cancellation flag. Now with TPL, it is build in with the CancellationTokenSource and CancellationToken.