Explained: Cancelling Tasks in .NET Core

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:
- CancellationTokenSource: This class is responsible for producing a
CancellationToken
. It also triggers the cancellation event, signaling any listening tasks that they should stop. - CancellationToken: This is the token passed to the task or operation that allows it to monitor whether cancellation has been requested.
- Checking for Cancellation: Tasks and methods can check for cancellation using either
token.IsCancellationRequested
or by callingtoken.ThrowIfCancellationRequested()
, which throws anOperationCanceledException
.
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:
- CancellationTokenSource: The
CancellationTokenSource
instance creates aCancellationToken
which is passed to the task. - Task.Run: In this example, we simulate user cancellation by triggering cancellation after 2 seconds.
- Cancellation Checking: Inside the long-running task (
LongRunningTaskAsync
), we usetoken.ThrowIfCancellationRequested()
to throw anOperationCanceledException
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:
- 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.
- CancellationToken in
StopAsync
: TheStopAsync
method receives aCancellationToken
, allowing the service to terminate gracefully. If needed, you could checkcancellationToken.IsCancellationRequested
within theDoWork
method to stop early if cancellation is requested.
How to Test CancellationToken
?
Testing task cancellation is straightforward:
- Triggering Cancellation: In unit tests or during development, you can manually call
cancellationTokenSource.Cancel()
at any point to simulate a user-triggered cancellation. - 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.