What happens when a user navigates away and still has a pending request to our server? Or what happens if we have a request which might take forever and wastes our resources?
CancellationToken
Taken the definition directly out of the Microsoft page:
A CancellationToken enables cooperative cancellation between threads, thread pool work items, or Task objects. You create a cancellation token by instantiating a CancellationTokenSource object, which manages cancellation tokens retrieved from its CancellationTokenSource.Token property. You then pass the cancellation token to any number of threads, tasks, or operations that should receive notice of cancellation.
The important part here is that we can cancel a Task
object, which is perfect. For that we just re-use the sample Blazor App.
Usage example
A very simple example how to use a CancellationToken
can be seen here:
var tokenSource = new CancellationTokenSource();
var t = DoWorkAsync(tokenSource.Token);
await Task.Delay(300);
tokenSource.Cancel();
await Task.Delay(300);
Console.Write("Done");
static async Task DoWorkAsync(CancellationToken token)
{
while (true)
{
if(token.IsCancellationRequested) return;
await Task.Delay(100);
Console.WriteLine("Still working...");
}
}
The output would be:
Still working...
Still working...
Still working...
Done
So we have a convenient way to cancel a Task
from the outside world. But wait there is more: We also have a wait to define a timeout where the token automatically get cancelled like this:var tokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));
. If we exchange that in our original code we get something like this now as output:
Still working...
Done
Blazor Cancellation when navigating away
The good thing is that the majority of the async API is supporting CancellationToken
s. That also includes library like Entity Framework Core. That is super convenient because we can save precious resources when a user is going away from our page but there are still open requests. And of course we can utilize this in Blazor as well.
Let's simulate a service which supports cancellation. For that we just use the existing WeatherForecastService
and make it cancelable with a small delay:
public async Task<WeatherForecast[]> GetForecastAsync(DateTime startDate, CancellationToken token)
{
await Task.Delay(2500);
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancelled");
return Array.Empty<WeatherForecast>();
}
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray();
}
Now the important part:
@page "/fetchdata"
<PageTitle>Weather forecast</PageTitle>
@using BlazorCancellation.Data
@inject WeatherForecastService ForecastService
@inject NavigationManager NavigationManager
@* left out intentionally *@
}
@code {
private readonly CancellationTokenSource tokenSource = new(TimeSpan.FromSeconds(3));
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
NavigationManager.LocationChanged += (_, _) => tokenSource.Cancel();
forecasts = await ForecastService.GetForecastAsync(DateTime.Now, tokenSource.Token);
}
}
Let's go through:
- We inject the
NavigationManager
because we need the hook into the event chain when the user navigates away private readonly CancellationTokenSource tokenSource = new(TimeSpan.FromSeconds(3));
will create aCancellationTokenSource
which automatically gets cancelled after 3 seconds. That means if the request takes longer than 3 seconds, we cancel the Task.NavigationManager.LocationChanged += (_, _) => tokenSource.Cancel()
- we want to cancel the task everytime when the user navigates away from our page.forecasts = await ForecastService.GetForecastAsync(DateTime.Now, tokenSource.Token);
as seen earlier we have to pass theToken
from theCancellationTokenSource
.
That's all! Super easy and convenient. If you now open the Fetch Data page and immediately navigate away you will see Cancelled
in your console.
You can also change the timeout to something small like: ``private readonly CancellationTokenSource tokenSource = new(TimeSpan.FromSeconds(1));` and even without navigating away you will see the message, plus after 2.5 seconds you see an empty table because in case of cancellation we return an empty array.
Conclusion
CancellationToken
s are a nice and convenient way to save some precious resources and help you scaling. You can also use them for an UI/UX point of view to know that some of the API's timed out. I'd encourage you to use CancellationToken
s more often.
Resources
- This sample can be found here
- All my blog examples can be found here
- More about
CancellationToken