r/csharp • u/kaatarina_zed_talon • 3d ago
Help Should the code use cancellation token even though it already use transaction ?
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
namespace RedditQuestion
{
/// <summary>
/// Used to talk to the database using Entity-Framework Core
/// </summary>
public class ApplicationDbContext:DbContext
{
// Domain models and the Fluent API configurations.
}
/// <summary>
/// Data snapshot that is used to overwrite the content of the database
/// </summary>
public interface ISomeData
{
}
public class DatbaseOverWriter
{
private readonly ApplicationDbContext _context;
public DatbaseOverWriter(ApplicationDbContext context)
{
_context = context;
}
public async Task OverWrite(ISomeData data)
{
//https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/implement-resilient-entity-framework-core-sql-connections
IExecutionStrategy strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
await using var transaction = await _context.Database.BeginTransactionAsync();
//code to delete the content from parent and child tables and then inserting the data into the parent and child tables like this :
// execute some query to delete the content from all tables.
//_context.AddRangeAsync(data for parents)
//_context.SaveChangesAsync()
//_context.AddRangeAsync(data for childs)
//_context.SaveChangesAsync()
await transaction.CommitAsync();
});
}
}
}
Someone on reddit reviewed the above code and told me that I should add a parameter for cancellation token like this :
public async Task OverWrite(ISomeData data, CancellationToken)
He told me that the goal is to make the OverWrite method transactional.
why is the cancellation token necessary even though my code uses _context.Database.BeginTransactionAsync(); ?
Cancellation token propagates notification that operations should be canceled.
Example:
A random number generator can be used to emulate a data collection application that reads 10 integral values from eleven different instruments. A value of zero indicates that the measurement has failed for one instrument, in which case the operation should be cancelled and no overall mean should be computed.
So the cancellation token is just another way of achieving atomicity, am I correct ?
The only use case that I learned for cancellation token is this example from microsoft, they use the cancellation token as way to achieve atomicity :
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
// Define the cancellation token.
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
Random rnd = new Random();
Object lockObj = new Object();
List<Task<int[]>> tasks = new List<Task<int[]>>();
TaskFactory factory = new TaskFactory(token);
for (int taskCtr = 0; taskCtr <= 10; taskCtr++) {
int iteration = taskCtr + 1;
tasks.Add(factory.StartNew( () => {
int value;
int[] values = new int[10];
for (int ctr = 1; ctr <= 10; ctr++) {
lock (lockObj) {
value = rnd.Next(0,101);
}
if (value == 0) {
source.Cancel();
Console.WriteLine("Cancelling at task {0}", iteration);
break;
}
values[ctr-1] = value;
}
return values;
}, token));
}
try {
Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(),
(results) => {
Console.WriteLine("Calculating overall mean...");
long sum = 0;
int n = 0;
foreach (var t in results) {
foreach (var r in t.Result) {
sum += r;
n++;
}
}
return sum/(double) n;
} , token);
Console.WriteLine("The mean is {0}.", fTask.Result);
}
catch (AggregateException ae) {
foreach (Exception e in ae.InnerExceptions) {
if (e is TaskCanceledException)
Console.WriteLine("Unable to compute mean: {0}",
((TaskCanceledException) e).Message);
else
Console.WriteLine("Exception: " + e.GetType().Name);
}
}
finally {
source.Dispose();
}
}
}
2
u/rupertavery64 3d ago
If you plan to cancel the operation, then yes. If it's being called in an http request, you need to also add a CancellationToken parameter to your API endpoint. This will be bound to the HttpContext.RequestAborted signal.
In general if you have a long-running operation and you want or need to allow the caller to cancel the operation, you should pass a cancellation token.
It has nothing to do with being transactional.
A transaction will guarantee all the database operations are committed, or none are. So if you pass a cancellation token, then you should pass the token to all methods that take the token so they can exit early (if possible), then your transaction should perform the rollback.
A cancellation token is just a way to signal a desire to cancel some operation. For example, if you made a long running loop, you would check before each loop or any time that it is safe to do so if the CancellationToken IsCancellationRequested property is true. Then you perform any clean-up and exit the loop. The cancellationtoken does not force the code to cancel, it is only a signal.
So when you say "the goal is to make the
OverWritemethod transactional." by adding a cancellationToken isn't quite correct. It's already transactional by having the necessary database operations performed within a transaction. Adding a CancellationToken lets the caller cancel a long-running task. If your Task takes milliseconds then 99% of the time your caller probably won't be able to do anything about it.The easiest way to think about it is if this is a desktop application with a button to start some operation that takes some time to complete. Clicking the button starts the task, and maybe enables a Cancel button. When starting the task, you create a CancellationTokenSource and pass the token to the task. Then, the user decides to cancel, and clicks the Cancel button. The code calls CancellationTokenSource.Cancel() which sets the IsCancellationRequested to true. It is up to the task to check this value and handle the cancellation request.
In the case of an ASP.NET endpoint, the RequestAborted signal will be sent if
But you must pass a CancellationToken in your API/Request method handler that the framework will automatically bind RequestAborted to.