Explained: Cancelling Tasks in .NET Core

Abhishek Thakur
4 min readSep 6, 2024

When building applications in .NET Core, especially when dealing with long-running tasks or asynchronous operations, it’s important to ensure that they can stop or exit gracefully when required. This is where the CancellationToken comes into play. In this article, we’ll dive deep into how you can leverage CancellationToken to manage task cancellation in a structured and graceful manner.

Why Use a CancellationToken?

Consider a long-running background task or a web service that needs to poll an external API repeatedly. If the application is shutting down or the task no longer needs to continue (due to user action or other conditions), it’s inefficient and potentially problematic to let the task run to completion. Instead, you want the task to stop as soon as possible and do so in a way that doesn’t cause unexpected behavior.

Enter CancellationToken — a cooperative mechanism that enables tasks and asynchronous operations to listen for a signal that tells them when they should stop.

How Does CancellationToken Work?

CancellationToken is part of a cooperative cancellation model in .NET. It does not forcefully terminate a task but instead provides a signal that tasks can respond to. It is up to the developer to ensure that the task or operation monitors this signal and handles cancellation appropriately.

Key Concepts:

  1. CancellationTokenSource: This class is responsible for producing a CancellationToken. It also triggers the cancellation event, signaling any listening tasks that they should stop.
  2. CancellationToken: This is the token passed to the task or operation that allows it to monitor whether cancellation has been requested.
  3. Checking for Cancellation: Tasks and methods can check for cancellation using either token.IsCancellationRequested or by calling token.ThrowIfCancellationRequested(), which throws an OperationCanceledException.

Basic Example

Let’s start with a simple example that demonstrates how a CancellationToken can be used to cancel an ongoing task.

using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
// Simulate user cancellation after 2 seconds
Task.Run(() =>
{
Thread.Sleep(2000);
cancellationTokenSource.Cancel();
});
try
{
Console.WriteLine("Starting long-running task...");
await LongRunningTaskAsync(token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Task was cancelled.");
}
finally
{
cancellationTokenSource.Dispose();
}
Console.WriteLine("Program completed.");
}

static async Task LongRunningTaskAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
// Check if cancellation is requested
token.ThrowIfCancellationRequested();
Console.WriteLine($"Working... {i}");
await Task.Delay(1000); // Simulate work
}
Console.WriteLine("Task completed successfully.");
}
}

Explanation:

  1. CancellationTokenSource: The CancellationTokenSource instance creates a CancellationToken which is passed to the task.
  2. Task.Run: In this example, we simulate user cancellation by triggering cancellation after 2 seconds.
  3. Cancellation Checking: Inside the long-running task (LongRunningTaskAsync), we use token.ThrowIfCancellationRequested() to throw an OperationCanceledException if cancellation is requested. This is the cooperative part where the task voluntarily stops its work.

Advanced Usage: Handling Long-Running Services

In a real-world application, especially in web APIs or background services, tasks often run for extended periods and may need to check for cancellation in loops, database queries, or while awaiting I/O operations.

Let’s take a look at an example of how CancellationToken can be used in a hosted background service in ASP.NET Core.

using Microsoft.Extensions.Hosting;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class TimedHostedService : IHostedService, IDisposable
{
private readonly IHttpClientFactory _httpClientFactory;
private Timer _timer;
public TimedHostedService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
return Task.CompletedTask;
}
private async void DoWork(object state)
{
Console.WriteLine("Fetching data from API...");
try
{
var client = _httpClientFactory.CreateClient();
var response = await client.GetAsync("https://your-api-endpoint");
response.EnsureSuccessStatusCode();
Console.WriteLine("Data fetched successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}

Key Points:

  1. Cancellation in Services: In long-running services or background tasks, cancellation is crucial to ensure the system can clean up resources when shutting down or restarting. In the example above, a timer executes an API call every 5 minutes.
  2. CancellationToken in StopAsync: The StopAsync method receives a CancellationToken, allowing the service to terminate gracefully. If needed, you could check cancellationToken.IsCancellationRequested within the DoWork method to stop early if cancellation is requested.

How to Test CancellationToken?

Testing task cancellation is straightforward:

  1. Triggering Cancellation: In unit tests or during development, you can manually call cancellationTokenSource.Cancel() at any point to simulate a user-triggered cancellation.
  2. Timeouts: You can also add a timeout to your token, ensuring tasks cancel automatically if they exceed a certain time limit:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

This will cancel the task if it runs for more than 5 seconds.

Handling Cancellations Gracefully

When working with cancellation tokens, it’s crucial to handle cancellations gracefully, ensuring that resources like file handles, database connections, or timers are released properly.

  • Use finally blocks to clean up resources after cancellation.
  • Monitor cancellation tokens in all long-running operations or network calls, and respond appropriately.

Conclusion

The CancellationToken pattern in .NET provides an excellent way to handle task cancellation in a clean, cooperative manner. Whether you're working with long-running background services or awaiting external API calls, CancellationToken ensures your tasks can exit gracefully when required.

By incorporating cancellation tokens, you not only improve the performance of your application but also make it more robust, ensuring that you can stop tasks on demand without leaving resources dangling or causing unexpected errors.

Final Thoughts:

By using CancellationToken effectively, you can handle real-world scenarios like task cancellation due to timeouts, user requests, or system shutdowns. Always aim to build applications that can terminate processes gracefully, allowing for a more maintainable and performant system.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response