Skip to main content

This recipe is about implementing the cancellation process for task-based asynchronous operations. We will learn how to use the cancellation token properly for tasks, and how to find out whether a task is cancelled before it was actually run.

using System;
using System.Threading;
using System.Threading.Tasks;

//
// Implementing a Cancellation Option
//
// This recipe is about implementing the cancellation process for task-based
// asynchronous operations. We will learn how to use the cancellation token
// properly for tasks, and how to find out whether a task is cancelled before it
// was actually run.
//
// How it works...
//
// This is another very simple example of how to implement the cancellation
// option for a TPL task, as you are already familiar with the cancellation
// token concept we discussed in Chapter 3, Using a Thread Pool.
//
// First, let's look closely at the longTask creation code. We'll pass a
// cancellation token to the underlying task once and then to the task
// constructor the second time. Why do we need to supply this token twice?
//
// The answer is that if we cancel the task before it was actually started, its
// TPL infrastructure is responsible for dealing with the cancellation, because
// our code will not execute at all. We know that the first task was canceled by
// getting its status. If we will try to call the Start method on this task, we
// will get InvalidOperationException.
//
// Then, we deal with the cancellation process from our own code. This means
// that we are now fully responsible for the cancellation process, and after we
// canceled the task, its status is still RanToCompletion, because from TPL's
// perspective, the task finished its job normally. It is very important to
// distinguish these two situations and understand the responsibility difference
// in each case.
//

namespace Chapter4.Recipe6
{
    class Program
    {
        private static void Main(string[] args)
        {
            var cts = new CancellationTokenSource();
            var longTask = new Task<int>(() => TaskMethod("Task 1", 10, cts.Token), cts.Token);
            Console.WriteLine(longTask.Status);
            cts.Cancel();
            Console.WriteLine(longTask.Status);
            Console.WriteLine("First task has been cancelled before execution");

            cts = new CancellationTokenSource();
            longTask = new Task<int>(() => TaskMethod("Task 2", 10, cts.Token), cts.Token);
            longTask.Start();
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine(longTask.Status);
            }
            cts.Cancel();

            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine(longTask.Status);
            }

            Console.WriteLine("A task has been completed with result {0}.", longTask.Result);
        }

        private static int TaskMethod(string name, int seconds, CancellationToken token)
        {
            Console.WriteLine(
            	"Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
            	name,
            	Thread.CurrentThread.ManagedThreadId,
            	Thread.CurrentThread.IsThreadPoolThread);

            for (int i = 0; i < seconds; i ++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));

                if (token.IsCancellationRequested)
                {
                    return -1;
                }
            }

            return 42 * seconds;
        }
    }
}