Cooperative Cancellation

1. What is the cooperative cancellation?

2. What are the types to work with the cooperative cancellation?

3. How to prevent a method from being canceled?

4. How to register a callback function to be invoked when a CancellationToken is cancelled?

5. How to prevent stopping of execution when a registered callback method throws exception?

6. How to unregister a callback method from being invoked when a CancellationToken is cancelled?

7. How to create linked CancellationTokenSource objects?

Cooperative cancellation is meaning that the operation that you wish to cancel has to explicitly support being cancelled.

         There are two types to support cancellation:

public CancellationTokenSource()

[ComVisibleAttribute(false)]
[HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, 
      ExternalThreading = true)]

public struct CancellationToken

To check if the Cancel() method was called we should check if IsCancellationRequested property returns true or false.

Sample 1:

        static void Main(string[] args)

        {

            CancellationTokenSource cts = new CancellationTokenSource();           

            ThreadPool.QueueUserWorkItem(o => DoWork(cts.Token, 100));

            Thread.Sleep(500);

            cts.Cancel();

            Console.ReadKey();

        }

        private static void DoWork(CancellationToken cancellationToken, int maxLength)

        {

            int i = 0;

            while (i < maxLength && !cancellationToken.IsCancellationRequested)

            {

                Trace.WriteLine(i++);

                Thread.Sleep(100);

            }

        }

To prevent method from being cancelled we can pass a result of the CancellationToken.None property which creates a special instance of the CancellationTokenSource, and other code can’t get access to this object, thus other code can’t call a Cancel() method.   

Sample 2:

        static void Main(string[] args)

        {

            CancellationTokenSource cts = new CancellationTokenSource();           

            ThreadPool.QueueUserWorkItem(o => DoWork(CancellationToken.None, 100));

            Thread.Sleep(500);

            cts.Cancel();

            Console.ReadKey();

        }

        private static void DoWork(CancellationToken cancellationToken, int maxLength)

        {

            int i = 0;

            while (i < maxLength && !cancellationToken.IsCancellationRequested)

            {

                Trace.WriteLine(i++);

                Thread.Sleep(100);

            }

        }

To register a callback function to be invoked when a CancellationToken is cancelled we must pass the callback method as the first parameter to the Register() function. If we want to make these methods invoked sequentially we should pass false as the second parameter otherwise it will be called using a thread’s SynchronizationContext.     

To prevent stopping of execution when a registered callback method throws exception we should pass false as the parameter to the Cancel() method. Thus execution won’t be stopped and we will be able to handle the exceptions thrown at callback methods handling the AggregateException.

Sample 3:

        static void Main(string[] args)

        {

            CancellationTokenSource cts = new CancellationTokenSource();

            ThreadPool.QueueUserWorkItem(o => DoWork(cts.Token, 100));

            Thread.Sleep(500);

            try

            {

                CancellationTokenRegistration ctr3 = cts.Token.Register(

                    () =>

                    {

                        Trace.WriteLine(“CancelCallback3 was called”);

                        throw new Exception(“Exception at the CancelCallback3”);

                    }

                    , true

                );

                CancellationTokenRegistration ctr2 = cts.Token.Register(

                    () =>

                    {

                        Trace.WriteLine(“CancelCallback2 was called”);

                        throw new Exception(“Exception at the CancelCallback2”);

                    }

                    , true

                );

                CancellationTokenRegistration ctr1 = cts.Token.Register(

                    () => Trace.WriteLine(“CancelCallback1 was called”), true);

                cts.Cancel(false);

            }

            catch (AggregateException ex)

            {

                foreach (Exception curEx in ex.InnerExceptions)

                {

                    Trace.WriteLine(curEx.ToString());

                }

            }

            Console.ReadKey();

        }

        private static void DoWork(CancellationToken cancellationToken, int maxLength)

        {

            int i = 0;

            while (i < maxLength && !cancellationToken.IsCancellationRequested)

            {

                Trace.WriteLine(i++);

                Thread.Sleep(100);

            }

        }

    }

To unregister a callback method from being invoked when a CancellationToken is cancelled we can call the Dispose for the instance of CancellationTokenRegistration returned after calling the Register() method.

Sample 4:

        static void Main(string[] args)

        {

            CancellationTokenSource cts = new CancellationTokenSource();

            ThreadPool.QueueUserWorkItem(o => DoWork(cts.Token, 100));

            Thread.Sleep(500);

            try

            {

                CancellationTokenRegistration ctr3 = cts.Token.Register(

                    () => Trace.WriteLine(“CancelCallback3 was called”), true);

                CancellationTokenRegistration ctr2 = cts.Token.Register(

                    () => Trace.WriteLine(“CancelCallback2 was called”), true);

                CancellationTokenRegistration ctr1 = cts.Token.Register(

                    () => Trace.WriteLine(“CancelCallback1 was called”), true);

                ctr1.Dispose();

                cts.Cancel(false);

            }

            catch (AggregateException ex)

            {

                foreach (Exception curEx in ex.InnerExceptions)

                {

                    Trace.WriteLine(curEx.ToString());

                }

            }

            Console.ReadKey();

        }

        private static void DoWork(CancellationToken cancellationToken, int maxLength)

        {

            int i = 0;

            while (i < maxLength && !cancellationToken.IsCancellationRequested)

            {

                Trace.WriteLine(i++);

                Thread.Sleep(100);

            }

        }

Output:

0

1

2

3

4

CancelCallback2 was called

CancelCallback3 was called

To create a linked CancellationTokenSource object we should call a CancellationTokenSource.CreateLinkedTokenSource() method. The linked object will be cancelled when any of the CancellationTokenSource objects are cancelled.

Sample 5:

        static void Main(string[] args)

        {

            CancellationTokenSource cts1 = new CancellationTokenSource();

            CancellationTokenSource cts2 = new CancellationTokenSource();

            CancellationTokenSource linkedCancellationTokenSource

                = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);

            ThreadPool.QueueUserWorkItem(o => DoWork(linkedCancellationTokenSource.Token, 100));

            Thread.Sleep(500);

            try

            {

                CancellationTokenRegistration ctr2 = linkedCancellationTokenSource.Token.Register(

                    () =>

                    {

                        Console.WriteLine(“LinkedCancellationTokenSourceCallback  was called”);

                        //throw new Exception(“Exception at the LinkedCancellationTokenSourceCallback”);

                    }

                    , true

                );

                CancellationTokenRegistration ctr1 = cts1.Token.Register(

                    () => Console.WriteLine(“CancelCallback1 was called”), true);

                cts1.Cancel();

            }

            catch (AggregateException ex)

            {

                foreach (Exception curEx in ex.InnerExceptions)

                {

                    Console.WriteLine(“Inner Exception: {0}”, curEx.ToString());

                }

            }

            Console.ReadKey();

        }

        private static void DoWork(CancellationToken cancellationToken, int maxLength)

        {

            int i = 0;

            while (i < maxLength && !cancellationToken.IsCancellationRequested)

            {

                Console.WriteLine(i++);

                Thread.Sleep(100);

            }

        }