Imagine you’re cooking dinner, and halfway through, you realize you’re out of an important ingredient. You’d want to stop cooking and run to the store, right? Well, in the coding world, CancellationToken is like that “stop cooking” signal. It’s a way to tell your code, “Hey, something’s come up, let’s pause this operation.”
In more technical terms, CancellationToken is a struct used to signal cancellation in multi-threaded applications. It’s like a red flag you can wave when you need a task to halt.
Understanding CancellationTokenSource
CancellationTokenSource is a class that provides a way to create and manage cancellation tokens. These tokens are used to signal and handle cancellation requests in a multi-threaded or asynchronous environment.
- Creating a CancellationTokenSource:
- You instantiate a
CancellationTokenSourceto create a new source of cancellation tokens. - Example:
var cts = new CancellationTokenSource();
- You instantiate a
- Generating a CancellationToken:
- From a
CancellationTokenSource, you generate aCancellationTokenwhich is used by tasks or operations to monitor for cancellation requests. - Example:
CancellationToken token = cts.Token;
- From a
- Signaling Cancellation:
- When you want to request cancellation, you call the
Cancelmethod on theCancellationTokenSource. - Example:
cts.Cancel();
- When you want to request cancellation, you call the
Properties and Methods
Here’s a breakdown of some important properties and methods of CancellationTokenSource:
- Properties:
Token: Gets theCancellationTokenassociated with thisCancellationTokenSource.IsCancellationRequested: Indicates whether cancellation has been requested for thisCancellationTokenSource.
- Methods:
Cancel(): Signals cancellation to all tasks or operations that are observing theCancellationToken.CancelAfter(TimeSpan delay): Schedules a cancellation request to be sent after the specified time delay.Dispose(): Releases resources used by CancellationTokenSource.TryReset(): Reset theCancellationTokenSourceso it can be reused for a new, unrelated operation. WARNING!!! This does not work when the cancellation has already happened.
Let’s look at a few examples to see how CancellationTokenSource can be used in different scenarios:
public void Example()
{
try
{
var cts = new CancellationTokenSource();
var token = cts.Token;
Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.WriteLine("Running...");
Thread.Sleep(1000);
}
});
Console.WriteLine("Press any key to cancel the operation");
Console.ReadKey();
cts.Cancel();
}
catch (OperationCanceledException ex)
{
Console.WriteLine($"The operation was cancelled. {ex}");
}
}
In the example above, when a button is pressed, the task will stop running.
public async Task TimeoutExample()
{
using var cts = new CancellationTokenSource();
var token = cts.Token;
cts.CancelAfter(TimeSpan.FromSeconds(5));
var task = Task.Run(() => DoWork(token), token);
try
{
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("Task was cancelled due to timeout.");
}
static void DoWork(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
Console.WriteLine($"Working... {i}");
Thread.Sleep(1000); // Simulate work
}
}
}
In this example:
- We use
cts.CancelAfter(TimeSpan.FromSeconds(5))to automatically cancel the task after 5 seconds. - The task respects this cancellation request and exits after the specified timeout.
public async Task LinkTokensExample()
{
using var cts1 = new CancellationTokenSource();
using var cts2 = new CancellationTokenSource();
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
var token = linkedCts.Token;
var task = Task.Run(() => DoWork(token), token);
// Simulate user canceling from either source
await Task.Delay(2000);
cts1.Cancel();
try
{
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("Task was cancelled from one of the sources.");
}
}
static void DoWork(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
Console.WriteLine($"Working... {i}");
Thread.Sleep(1000); // Simulate work
}
}
Sometimes, you might need to handle multiple cancellation sources. You can combine them using the method CancellationTokenSource.CreateLinkedTokenSource().
In this example:
- We create two
CancellationTokenSourceinstances. - We combine them using
CancellationTokenSource.CreateLinkedTokenSource(). - Cancelling any of the original sources (
cts1orcts2) will cancel the task.
app.MapGet("/fetchdata", async (CancellationToken cancellationToken) =>
{
try
{
var data = await FetchDataAsync(cancellationToken);
return Results.Ok(data);
}
catch (OperationCanceledException)
{
return Results.Problem("The request was cancelled.");
}
});
async Task<string> FetchDataAsync(CancellationToken cancellationToken)
{
for (int i = 0; i < 10; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(1000, cancellationToken); // Simulate work
}
return "Fetched data successfully!";
}
In the example above:
- API Endpoint: The endpoint
/fetchdatatakes aCancellationTokenas a parameter. This token is passed by the ASP.NET Core runtime and is tied to the HTTP request lifecycle.- When a client cancels the HTTP request (e.g., by closing the browser tab), the runtime signals the
CancellationTokenassociated with that request.
- When a client cancels the HTTP request (e.g., by closing the browser tab), the runtime signals the
- Task Execution: Inside the
FetchDataAsyncmethod, the token is periodically checked usingcancellationToken.ThrowIfCancellationRequested().- If the client cancels the request, the token is signaled, and
ThrowIfCancellationRequestedthrows anOperationCanceledException.
- If the client cancels the request, the token is signaled, and
- Handling Cancellation: The exception is caught in the endpoint, and an appropriate response is returned to indicate that the task was canceled.
If you are using EF Core, you can pass the token to the DBContext and EF Core will handle the cancellation for you.
static async Task FetchProductsAsync(CancellationToken token)
{
using var context = new ApplicationDbContext();
var products = await context.Products
.Where(p => p.Price > 10)
.ToListAsync(token); // Pass the CancellationToken here
foreach (var product in products)
{
Console.WriteLine($"Product: {product.Name}, Price: {product.Price}");
}
}
Why Should You Care?
Using CancellationToken and CancellationTokenSource makes your applications more responsive and user-friendly. Users can cancel operations that are taking too long, and your app can gracefully handle these interruptions without crashing or freezing. Imagine a malicious user causing multiple long running queries and cancelling them, would your application handle it?
Wrapping Up
CancellationTokenSource is a powerful tool for managing cancellations in C#. It allows you to create, signal, and handle cancellation requests gracefully.
And there you have it! A quick introduction to CancellationToken and CancellationTokenSource in C#. These tools are essential for writing responsive and user-friendly applications. So next time you’re working on a long-running task or an API give these a try and see how they make your life easier.
Affiliate promo
If you love learning new stuff and want to support me, consider buying a course from Dometrain using this link: Browse courses – Dometrain. Thank you!
Leave a comment