C# – Task and Cancellation Token

By | 20/07/2022

In this post, we will see how to use a Cancellation Token in a Task.
But first of all, what is a Cancellation Token?
From Microsoft web site:
A CancellationToken enables cooperative cancellation between threads, thread pool work items, or Task objects“.
In a nutshell, when we run a Task and we decide to cancel it, using a cancellation token we will stop immediately the task otherwise, it will continue until the end of the operation.
We will see how to use a Cancellation Token in a Console application and in a Web API project.


CONSOLE APPLICATION
In this example, we will develop a Console application that it will create a text file with 20 rows inside.
Obviously, the goal of this test is to understand how to use a Cancellation token and the purpose of the method is irrelevant.

We start creating a Console Application called TestCancellationToken where we add a class called Core:

[CORE.CS]

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

namespace TestCancellationToken
{
    public class Core
    {
        public async Task GenericMethod()
        {
            string fileName = @"c:\test\";
            DateTime start;
            DateTime end;

            try
            {
                using (StreamWriter outputFile = new StreamWriter(Path.Combine(fileName, "WriteLines2.txt")))
                {
                    start = DateTime.Now;
                    for (int i = 1; i < 20; i++)
                    {
                        Console.WriteLine(i.ToString());
                        await outputFile.WriteLineAsync($"Row{i}");
                        await Task.Delay(1000);
                    }
                    end = DateTime.Now;
                    await outputFile.WriteLineAsync($"Totale seconds: {(end - start).TotalSeconds}");
                    Console.WriteLine($"Totale seconds: {(end - start).TotalSeconds}");
                }
            }
            catch (Exception)
            {
                Console.WriteLine("Exception");
                throw;
            }
        }
    }
}



Then, we modify the file Program.cs:
[PROGRAM.CS]

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

namespace TestCancellationToken
{
    public class Program
    {
        static async Task Main(string[] args)
        {
            Core objCore = new Core();

            try
            {
                Console.WriteLine("Push a button to abort the operation");
                objCore.GenericMethod();
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}



We have done and, if we run the application, this will be the result:


Now, we will run again the application but we will stop it after 6 seconds:

We can see that the application creates the file but didn’t write anything inside.


Now, we will modify our code in order to use a Cancellation Task and we will see how the application will work.
First of all, we modify the Class Core:

[CORE.CS]

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

namespace TestCancellationToken
{
    public class Core
    {
        // we pass in input a Cancellation Token
        public async Task GenericMethod(CancellationToken token)
        {
            string fileName = @"c:\test\";
            DateTime start;
            DateTime end;

            try
            {
                using (StreamWriter outputFile = new StreamWriter(Path.Combine(fileName, "WriteLines2.txt")))
                {
                    start = DateTime.Now;
                    for (int i = 1; i < 20; i++)
                    {
                        Console.WriteLine(i.ToString());
                        // we pass the cancellation token in the method WriteLineAsync
                        await outputFile.WriteLineAsync($"Row{i}".AsMemory(), token);
                        await Task.Delay(1000);
                    }
                    end = DateTime.Now;
                    await outputFile.WriteLineAsync($"Totale seconds: {(end - start).TotalSeconds}");
                    Console.WriteLine($"Totale seconds: {(end - start).TotalSeconds}");
                }
            }
            catch (OperationCanceledException)
            {
                // there is a request for a cancellation
                Console.WriteLine("The operation has been cancelled");
                throw;
            }
        }
    }
}



Finally, we modify Programm.cs:

[PROGRAM.CS]

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

namespace TestCancellationToken
{
    public class Program
    {
        static async Task Main(string[] args)
        {
            // CacellationTokenSource is yhe object responsible for creating a 
            // Cancellation Token
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            // This is the Canellation Token
            CancellationToken token = tokenSource.Token;
            Core objCore = new Core();

            try
            {
                Console.WriteLine("Push a button to abort the operation");
                // We pass the token in input at this method
                objCore.GenericMethod(token);
                Console.ReadLine();
                // it communicates a request for a cancellation
                tokenSource.Cancel();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                // it is a best practice run the Dispose 
                tokenSource.Dispose();
                await Task.Delay(1000);
            }
        }
    }
}



We have done and, if we run the application, this will be the result:


Obviously, everything works fine but now, we will see how it will work if we stop the execution after 5/6 seconds:

We can see that with a Cancellation Token the system wrote in the file until we sent the Cancellation Token because, there has been communication between different Tasks.



WEB API
In this example, we will run the same method to create a txt file but using a Web API.
We start creating a Web API project called TestCancellationToken where, we will add a controller called SaveController:

[SAVECONTROLLER.CS]

using Microsoft.AspNetCore.Mvc;
using System;
using System.IO;
using System.Threading.Tasks;

namespace WebApiCancellationToken.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class SaveController : ControllerBase
    {
        [HttpGet]
        public async Task<IActionResult> Save()
        {
            string fileName = @"c:\test\";
            DateTime start;
            DateTime end;

            try
            {
                using (StreamWriter outputFile = new StreamWriter(Path.Combine(fileName, "WriteLines.txt")))
                {
                    start = DateTime.Now;
                    for (int i = 1; i < 20; i++)
                    {
                        await outputFile.WriteLineAsync($"Row{i}");
                        await Task.Delay(1000);
                    }
                    end = DateTime.Now;
                    await outputFile.WriteLineAsync($"Totale seconds: {(end - start).TotalSeconds}");
                }
            }
            catch (Exception)
            {
                throw;
            }

            return Ok($"Totale seconds: {(end - start).TotalSeconds}");
        }
    }
}



If we run the Web API, this will be the result:

We can see that it works fine.
But, if we run the method and then we cancel the request after 6 seconds, what will be the result?

We can see that despite of we cancelled the request, the Task continued to work and at the end, it has created the file.
This isn’t a good behaviour because, it means that we have used CPU for a job that we didn’t want anymore.
Furthermore in this case the job was easy and simple but, we have to think that in a real project we could have complicated jobs that can consume a lot of CPU, space on system or database and bandwidth.
So, it would be better stop the job if we decide to cancel the request.
In order to do it, we can use a Cancellation Token:

[SAVECONTROLLER.CS]

using Microsoft.AspNetCore.Mvc;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace WebApiCancellationToken.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class SaveController : ControllerBase
    {
        [HttpGet]
        public async Task<IActionResult> Save(CancellationToken token)
        {
            string fileName = @"c:\test\";
            DateTime start;
            DateTime end;

            try
            {
                using (StreamWriter outputFile = new StreamWriter(Path.Combine(fileName, "WriteLines.txt")))
                {
                    start = DateTime.Now;
                    for (int i = 1; i < 20; i++)
                    {
                        await outputFile.WriteLineAsync($"Row{i}".AsMemory(), token);
                        await Task.Delay(1000);
                    }
                    end = DateTime.Now;
                    await outputFile.WriteLineAsync($"Totale seconds: {(end - start).TotalSeconds}");
                }
            }
            catch (OperationCanceledException)
            {
                throw;
            }

            return Ok($"Totale seconds: {(end - start).TotalSeconds}");
        }
    }
}



Now, first of all, we check that everything works fine:


‘Obviously’, it works fine!
Finally, we try to run it again but, after 10 seconds, we will cancel the request:

With the Cancellation Token, we have a correct behaviour.
In fact, we can see that it didn’t create the entire file but, it created the file until we requested the cancellation.


Leave a Reply

Your email address will not be published. Required fields are marked *