Cancellation token multifold
Combine token sources to get the best out of cancellation model
This post is a continuation of my previous one Cancellation token and APIs😇
You should check that out for an intro to cancellation tokens and how we can use them in APIs.
In this post we will take a step further and take a look at
- How to cancel multiple tasks using the same cancellation token
- Link multiple token sources
- Track multiple cancellation tokens simultaneously so that we can cancel a task if either token requests cancellation
- Parent token, child token scenarios
Cancel multiple tasks using the same cancellation token
This one is pretty straight forward, we pass on the same token
to multiple tasks
, if cancellation is requested, all tasks are cancelled.
[HttpPost("~/longoperation")]
public async Task LongRunningOperation(CancellationToken token)
{
_logger.LogInformation("Calling long running operations");
Task one = LongRunningOperationOneAsync(token);
Task two = LongRunningOperationTwoAsync(token);
await Task.WhenAll(one, two);
_logger.LogInformation("Done with long running operations");
}
private async Task LongRunningOperationOneAsync(CancellationToken token)
{
await Task.Delay(1000);
if (token.IsCancellationRequested)
{
_logger.LogError("LongRunningOperationOneAsync was cancelled");
}
}
private async Task LongRunningOperationTwoAsync(CancellationToken token)
{
await Task.Delay(1000);
if (token.IsCancellationRequested)
{
_logger.LogError("LongRunningOperationTwoAsync was cancelled");
}
}
After I initiated the request using Postman
, I immediately hit cancel
and this is how the application behaves.
When a cancellation request was encountered, both tasks processed it.
Link multiple token sources
âš¡ Track multiple cancellation tokens simultaneously
Leverage CreateLinkedTokenSource
to combine multiple tokens and build a CancellationTokenSource
. This tokensource would start monitoring cancellation in any of the encapsulating tokens.
In our case, we created a new CancellationTokenSource and set it up to auto cancel
after 500ms
, and then we also have the token that is injected
via the API request
. We make use of CreateLinkedTokenSource method to generate a new CancellationTokenSource from the 2 tokens, which is then used further in our long running operation.
[HttpPost("~/longoperation")]
public async Task LongRunningOperation(CancellationToken token)
{
CancellationTokenSource otherTokenSource = new CancellationTokenSource();
otherTokenSource.CancelAfter(500);
// create a new tokensource out of available 2 cancellation tokens
CancellationTokenSource tokenSourceToUse = CancellationTokenSource.CreateLinkedTokenSource(token, otherTokenSource.Token);
_logger.LogInformation("Calling long running operation");
await LongRunningOperationOneAsync(tokenSourceToUse.Token);
_logger.LogInformation("Done with long running operation");
}
private async Task LongRunningOperationOneAsync(CancellationToken token)
{
await Task.Delay(1000);
if (token.IsCancellationRequested)
{
_logger.LogError("LongRunningOperationOneAsync was cancelled");
}
}
After I initiated the request using Postman
, I waited for sometime and this is how the application behaves.
Since our token triggers cancellation after 500ms
automatically, our long running operation gets cancelled.
âš¡ Parent token, child token scenarios
There are times when you would want to cancel child tasks
but continue your parent tasks
. For cases like these, we again leverage an overload of the CreateLinkedTokenSource
method.
[HttpPost("~/longoperation")]
public async Task LongRunningOperation(CancellationToken parentToken)
{
// create a tokensource out of parentToken
CancellationTokenSource childTokenSource = CancellationTokenSource.CreateLinkedTokenSource(parentToken);
childTokenSource.CancelAfter(500);
_logger.LogInformation("Calling long running operation");
await LongRunningOperationOneAsync(childTokenSource.Token);
if (!parentToken.IsCancellationRequested)
{
_logger.LogInformation("Parent cancellation was never requested, continue processing 100 other operations");
}
_logger.LogInformation("Done with long running operation");
}
private async Task LongRunningOperationOneAsync(CancellationToken token)
{
await Task.Delay(1000);
if (token.IsCancellationRequested)
{
_logger.LogError("Child cancellation was requested");
}
}
After I initiated the request using Postman
, I waited for sometime and this is how the application behaves.
Since our child token triggers cancellation after 500ms
automatically, our child task gets cancelled, but our parent token still continues to be available.
💡 Note
If parent token is cancelled, all child tokens will be cancelled automatically.
The Task Parallel Library
provides us with a wide range of easy to use extensions around cancellation tokens, we can setup cancellation behavior for various mix and matches.
I hope this post gave you some insights on how we can leverage CancellationTokens. Thank you for reading. Cheers!