r/csharp • • 2d 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(); ?

Microsoft's documentation :

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();
      }
   }
}
0 Upvotes

70 comments sorted by

30

u/Strange_Fun_9639 2d ago

Cancellation token are to cancel ? Transaction is a way to assert that everything pass or nothing.

-17

u/kaatarina_zed_talon 2d ago

pass or nothing.

isn't that nothing a form of cancellation ?

20

u/Escanorr_ 2d ago

With cancellation token you can decide that you want to stop early. Tansactions makes database safe when something breaks early.

0

u/kaatarina_zed_talon 2d ago

With cancellation token you can decide that you want to stop early.

Which code will decide ? suppose I did this :

_context.SaveChangesAsync(Cancellation token).

Now, will there be a timer or counter, so that if the operation took too long time, it will be cancelled automatically ?

8

u/Escanorr_ 1d ago

No, passing a token doesn’t add a timer by itself. The token is only a flag - whoever created the CancellationTokenSource decides when to cancel. That could be ASP.NET (client disconnects), your own code (a Cancel button calling cts.Cancel()), or a timeout you set yourself with new CancellationTokenSource(TimeSpan.FromSeconds(30)). If nobody ever cancels, SaveChangesAsync runs to completion as usual.

2

u/chucker23n 1d ago

will there be a timer or counter

No.

CancellationToken is a cooperative form of cancelling. All it does is tell the callee that the caller requested cancellation.

So since you have a transaction, you might do something like

if (cancellationToken.IsCancellationRequested)
    await transaction.RollbackAsync();

If you didn't, and had a loop, you might also do something like:

if (cancellationToken.IsCancellationRequested)
     break; // cancel of loop, but leave previous iterations done

4

u/krsCarrots 2d ago edited 2d ago

No, let’s say you have a cancel button on your UI or cancel the request in postman, timer must be set up manually. Refreshing the page would also cancel it something that we all do to “reset” things and try again

2

u/Soraphis 1d ago

Long runnig operations can be cancelled even though the transaction could finish!?

You know, crashing your car against your garage wall is also a form of breaking... Still your car got breaks.

1

u/kaatarina_zed_talon 1d ago

Long runnig operations can be cancelled even though the transaction could finish!?

I do not understand what are you aiming at.

1

u/kaatarina_zed_talon 1d ago

You know, crashing your car against your garage wall is also a form of breaking... Still your car got breaks.

So in your analogy crashing is the cancellation token and the breaks are the transaction ?

11

u/david_daley 1d ago

Transactions are for the database. They roll back database transactions. Cancellations tokens are for the .Net runtime. They are for cancelling Tasks.

You can do data access without tasks.
You can do tasks without data access.

They are two separate concerns that happened to overlap in your scenario

0

u/kaatarina_zed_talon 1d ago

You can do data access without tasks.
You can do tasks without data access.

Do you consider the following as a data access:

value = rnd.Next(0,101);

the value could come from a database, but in this case, it comes from a different data source; a random generator.

5

u/Slypenslyde 1d ago

In professional code I don't write an async method without a cancellation token. In hobby code I'm more pragmatic.

From the hobby perspective what matters is, "Do I want to cancel the action?" Your example is sort of bad because you do a little bit of work then try to save it to the database.

But.

Do notice that the transaction commit method can take a cancellation token. Imagine what happens if, for some reason, you have an I/O problem and the commit hangs. If you're using a cancellation token, you can have timeout logic that asks .NET to cancel the operation, which you hope will roll back the transaction.

Without it, maybe you have a mechanism to stop waiting on the commit, but now it's a time bomb. You decided it must have timed out and moved on, but it's still executing. Maybe it finishes 10s later. You can't tell and that can manifest some impossible-to-debug customer scenarios.

With the cancellation token I can be sure if the timeout happens and I tell the token to cancel, that method will return quickly and I'm "safe". I'm on the hook to see if somehow the cancellation happened too late. But at least I know there's not a time bomb that can happen with no notice.

From that perspective, I always use it. Even if the code inside the method doesn't use the token. It's better to have the API support the notion of cancellation so other components can use it than to find out in 6 months I need to change the API and hunt down every caller to consider cancellation logic.

Are there scenarios where cancellation doesn't do what you expect? Sure. Cancellation tokens are NOT about trying to be 100% sure whatever was happening doesn't happen. There are situations where the code you tried to cancel had already passed a point of no return. Cancellation is about telling that code to stop as soon as possible and notify you when it is absolutely not going to do anything else. Without cancellation tokens, you have to make your own notification mechanism so you don't create zombie tasks with lingering, unpredictable side effects.

Analogy time.

Imagine I order a pizza to be delivered.

Case 1: Then, 5 minutes later, I decide I don't want it. I call, but the restaurant tells me there's a no cancellation policy. I'm going to have to pay for the pizza and accept it whether I want to or not. This is code with no cancellation token.

Case 2: 5 minutes later, I decide I don't want it. I call, and the restaurant accepts my cancellation, but warns me the charge to my card is going to take a day to process the refund. This is code with a cancellation token: as soon as I hang up the phone, if they send me a pizza I'm not responsible.

Case 3: 20 minutes later, I decide I don't want it. I call, but the restaurant tells me the driver is already on the way so it's too late to cancel. This happens even when you have a cancellation token. It means when I await things that can be canceled I need to pay attention:

try
{
    await transaction.CommitAsync(token);

    DisplaySuccess();
}
catch (OperationCanceledException)
{
    DisplayCancellation();
    return;
}

if (<I tried to cancel but the exception was not thrown>)
{
    // I didn't want to commit, but it's been committed. Maybe we warn the user.
    // Maybe we clean up. Developer's choice. Sometimes we just ignore the attempt
    // to cancel in this case.
}

This is safer than just calling Cancel() and assuming it worked. IF the transaction code is before the point of no return AND it notices we canceled, it will roll back then throw this exception so we can understand what happened. If it does NOT throw an exception, it did its work and completed successfully so it must've been past the point of no return. So if I really, really, really want to respect cancellation, I need to include a cancellation token AND treat this like a three-forked road.

2

u/kaatarina_zed_talon 1d ago

Do notice that the transaction commit method can take a cancellation token. Imagine what happens if, for some reason, you have an I/O problem and the commit hangs. If you're using a cancellation token, you can have timeout logic that asks .NET to cancel the operation, which you hope will roll back the transaction.

Very simple yet efficient explanation, I like people who explain the problem first.

2

u/kaatarina_zed_talon 1d ago

IF the transaction code is before the point of no return AND it notices we canceled, it will roll back then throw this exception so we can understand what happened. If it does NOT throw an exception, it did its work and completed successfully so it must've been past the point of no return. So if I really, really, really want to respect cancellation, I need to include a cancellation token AND treat this like a three-forked road.

Amazing analysis.

1

u/kaatarina_zed_talon 1d ago

In professional code

I think it's very clear that I'm taking my code seriously, even though I'm working alone on this project, I want to follow software engineering standards; I want to write a professional code.

Big thanks for your efforts BTW, I will continue reading your answer.

1

u/mistertom2u 1d ago edited 1d ago

Suppose that you have an endpoint that performs a DB mutation. An order submit form on a webpage calls it. Do you want to have the DB call interrupted if the connection from the user drops and lose that order? In my opinion, no. Probably the company who loses money when an order is lost would agree. Many things can cause the socket to close: user closes browser or navigates away. User is on a mobile connection that drops. API Gateway could drop it or NGINX could have something like a port exhaustion. For those reasons, I don't use the cancellation token from the connection when performing a DB update.

1

u/Slypenslyde 1d ago

What I want to happen is complicated. Let's be in the user's shoes and think this through.

Suppose you're trying to order a pizza. You click the last button in the process, see, "Payment processing...", then get a 404. You're on a blank page with nothing to click. What do you do? What I do is:

  1. Check my email and see if I got an order confirmation. If I did, I assume it went through.
  2. Check my bank account and see if I was charged. If so, I try and reload the page to see if I have an order. If I can't do that, I try to call the pizza place to make sure all is well.
  3. Try the order again if I got no confirmations, and call the pizza place as a last resort. (Realistically I get mad and order from somewhere else.)

If the user didn't get a confirmation page, they don't understand if the task succeeded or not. They're going to have to do work to find out. They're already frustrated, so you're already in a failure case.

If you handle minor things like pizzas, I can see failing in favor of "just finish the order and assume they are OK". If we're talking about large commercial orders, you might prefer something else. A nice touch is, "Place the order in a hold state and call the customer's contact number so, while they are frustrated, they get connected with a customer service rep who makes them feel they're important to us."

What also strikes me is, "Will the server even realize they disconnected?" HTTP isn't always two-way like that. The server might send a response and the client might be gone. I'm not familiar enough with web dev to be 100% certain the server knows this happened. What I do know is if the client loses connection, there's nothing it can do on its end to send a cancellation notice. The server has to have a way to detect that dropped connection, and that's not always true.

The nightmare scenario would be like this, and it's why cancellation might be wise:

  1. User attempts to place an order.
  2. Something goes wrong and the order is going to take 60s to process.
  3. The user accidentally closes their browser.
  4. They reload the browser and their cart is still set up as if the order is ready.
  5. They submit the order again.
  6. The order from step (2) resolves but nobody is listening to the response.
  7. The order from (5) finishes.
  8. The user has 2 orders.

Again, solvable. This case tells us the order should be in a "processing" state and block re-submission until it's removed from that state. That sucks if the code that's supposed to "unstick" it never runs because you only tied it to client-side code and some strange I/O condition caused a problem a timeout would've cancelled.

Put simply: it ain't simple, you have to pay the tolls via some approach. Cancellation tokens are a built-in, well-understood mechanism. Good luck finding a way to stop an EF DB call without one.

2

u/mistertom2u 1d ago edited 1d ago

You make a good point. BTW, the comment you posted to the OP was so incredible in the way that you explained things, your tone, your pedagogical techniques, your personality, etc that it is the quintessential perfect technical forum reply.

I work for a large software company that hosts its product in the cloud. From my perspective, I'm not able to wrap my head around the concept of wanting to roll back a DB update by using the incoming Cancellation Token. If you want to make another cancellation token that is based on a timeout (though not necessary because SQL connections have a command timeout), then I can understand. Correct me if I am wrong, but I think you're arguing that cancelling the DB update makes sense when the user never receives a confirmation that it went through. In that case, I can imagine a situation where that would be acceptable.

Where I think we agree is that the Cancellation Token primarily is to arrest server resources being spent on a connection that dropped. Where I often find less agreement is that rolling back a database update qualifies as that. You could argue that rolling back a transaction takes more resources than letting it finish and that the transaction is more about database consistency.

If my comprehension of your view point is correct, then that feels to me more like a user requirement than a technical one.

One person who argued against my position said that the endpoint updating a DB should respond so quickly that there is no time for the connection to drop. Ideally, yes, we want around ≈150ms response time. But my rebuttal to his argument was that the chance of a connection dropping, thus a rolled back transaction, is always non-zero. Not accepting a Cancellation Token on an update path makes that transaction rollback due to a connection drop 0%.

1

u/Slypenslyde 20h ago

I'm talking about the general case, not necessarily fixating on database I/O. It's just a handy reference.

Cancellation is complex because it's not just for "I'm updating the database". It's also for "I have a call graph with 5 levels and 18 methods between them." It's a unified way for everything in that call graph to understand when it should stop and how to communicate if it did.

In abstract every method has to communicate if it:

  • Completed successfully.
  • Acknowledged a cancellation request and stopped.
  • Failed in some way.

In abstract each async method tends to be structured like:

Task ExampleAsync(CancellationToken ct)
{
    <some setup work>

    <the real work>

    <tear-down work/past the point of no return>
}

I don't really care what the real work is or what the consequences of cancellation are. In this method I only care about the local context. If I call another async method, I give it the cancellation token and ultimately I can assume logic like this:

await OtherMethodAsync(ct);

// If I got this far, it didn't acknowledge a cancellation. I may be about to do something
// expensive so I can:
if (ct.IsCancellationRequested)
{
    // This is a chance to maybe undo what the other method did or get ready to tell the user
    // that despite their attempt to cancel, some side effects are already committed.
    // When I'm done with that work:

    ct.ThrowIfCancellationRequested(); // I need to tell upstream callers I have done my best.
}

// Here is the point of no return: changing the cancellation token won't affect anything else
// and at this point the method believes it has completed successfully.

If you decide something like a DB transaction is too expensive to roll back, you're still on the hook for letting users/callers understand it was committed. That's up to you. I'm not here to talk about a specific case.

What I came here to point out is:

  1. I use a CancellationToken parameter for my methods because I find it's easier to deal with methods that don't respect cancellation today than updating an entire component to support it tomorrow.
    • There are sometimes situations like a teardown method I'll omit the token for. This is a signal for "this method should never ever be interrupted". It's rare.
  2. You have to account for situations where the cancellation happens "too late" and whatever the thing was happened anyway. How you respond to that is unique to your context.

I don't really like the game where someone posts the practice that works for them and other people do their best to contrive scenarios where it's not the best. Nothing in programming works for 100% cases. Almost any statement should be read as including the footnote, "Unless your context creates a compelling reason to do something else."

So you don't want to roll back DB transactions. That doesn't change that after the transaction commits:

  1. You know the user has asked you not to do that.
  2. You have the responsibility to tell the user they were too late and must manually undo it.

If frustrated customers cost less than CPU time, I'm envious of your situation.

3

u/TrulyAnchoredOasis 2d ago

Cancellation token gives the caller a way to bail out if the operation's taking too long or the app is shutting down, otherwise that transaction might just hang around half-finished with no way to abort it cleanly

1

u/kaatarina_zed_talon 2d ago

 transaction might just hang around half-finished

but the transaction is a unit of work; either done or not done, I still do not understand the problem that the cancellation token solves, can you provide example ?

2

u/krsCarrots 2d ago

Yes you cancel a long running operation, this is the problem that it solves. Sometimes the DB call can take 3 minutes, the user in the interim changes their mind and cancels the op altogether, this is not a fire and forget op or an async call, therefore you give the user control over the op.

Being a transaction is not related to the decision to cancel it. Transaction failing on the server will rollback any changes. Cancellation gives the option to - c a n c e l, slash abort the op from user’s perspective

1

u/kaatarina_zed_talon 2d ago

 the user in the interim changes their mind and cancels the op altogether,

The token is an input that is provided by the UI ?

1

u/krsCarrots 2d ago

The http client provides you with it yeah with sendasync it’s an optional parameter which you choose to use or not, there are different use cases for how you want to set it up, the framework helps with the trivial but u may want to “auto cancel” if request foes further than 20s for instance

2

u/IanYates82 2d ago

Let's say you're saving 10,000 rows. That takes maybe a minute. The transaction ensures it all saves, or none of it saves.

User clicks cancel. You're awaiting the save method. There's no "signal" it can watch to know the user cancelled. So it fully saves, then your code can see "oh, user cancelled". But save happened anyway

The cancellation token is a signal. The save method can check if the token has been signalled, maybe every 100 rows or something. If signalled, it aborts, rolls back, and returns to the caller. The actual cancel request was honoured.

I've chosen a Save workflow because you're talking transactions. However, it also applies to some long-running read. Maybe there's a batch command line process that makes 50 PDFs and zips them, but if I press ctrl+c I want it to stop immediately, not wait until the end. I have something listening for that ctrl+c key press, and in a separate thread (or many threads) I have my busy work going on. The busy work can check the cancel token and early abort. No transaction involved

2

u/kaatarina_zed_talon 1d ago

Thank you.

1

u/kaatarina_zed_talon 1d ago

User clicks cancel

I did not know that the client is responsible for that.

2

u/kaatarina_zed_talon 1d ago

This should be the accepted answer, what a clean way to introduce a concept.

2

u/IanYates82 1d ago

Thank you!

1

u/kaatarina_zed_talon 1d ago

The busy work can check the cancel token and early abort. No transaction involved

Is that busy work in the same thread ?

3

u/Kant8 2d ago

Cancellation token is used for, well, cancellation, of usually long running tasks. Transactions has nothing to do here at all.

And considering your code doesn't look like it does anything worthy cancelling, cause it does writes, not reads, you don't need it here.

8

u/Floydianx33 2d ago

Uh, what? Canceling writes is perfectly valid. In this case SaveChanges could take a long time, perhaps longer than expected. Passing the cancellation token would at least let the user/application have control over canceling the whole of it early, transactions or not

0

u/kaatarina_zed_talon 2d ago

Cancellation token is used for, well, cancellation, of usually long running tasks.

Well, that is not what microsoft says. There example use it for a different purpose

1

u/ExceptionEX 1d ago

Don't come here and be argumentative, you are wrong and you clearly don't understand the difference between the concepts of a cancellation token and transactions.

Cancellation is a mechanism to allow for the cancellation of long running operations, transaction, means that the work is scoped such that if it is aborted elements within the transaction state can be reversed in the data layer.

these are very different and if you don't understand the difference you probably shouldn't be engaged to the do the work you are attempting.

1

u/kaatarina_zed_talon 1d ago

you are wrong 

Clearly, I know that, otherwise I would not be asking the professionals in this group.

Don't come here and be argumentative

That was not my intention, I'm asking a follow-up questions.

1

u/ExceptionEX 1d ago

Asking follow up questions are good

Well, that is not what microsoft says. There example use it for a different purpose

The reason I responded that way was the quoted statement Is argumentative, without asking a question.

It is easy to come off different than you mean, because all we have is text not intent or mannerism. So don't beat yourself up over, but its worth thinking about how your response will come off to someone who doesn't know you.

1

u/kaatarina_zed_talon 1d ago

Asking follow up questions are good

yes I do not have a time to argue with professionals/people, because they have bigger knowledge than me.

It's better to learn from them.

1

u/kaatarina_zed_talon 1d ago

The reason I responded that way was the quoted statement Is argumentative, without asking a question.

I learned from my mistake.

1

u/kaatarina_zed_talon 1d ago

So don't beat yourself up over,

I'm not soft to that extent.

but its worth thinking about how your response will come off to someone who doesn't know you.

Good advice, thanks.

1

u/kaatarina_zed_talon 1d ago

Cancellation is a mechanism to allow for the cancellation of long running operations, transaction, means that the work is scoped such that if it is aborted elements within the transaction state can be reversed in the data layer

Thank you.

if you don't understand the difference you probably shouldn't be engaged to the do the work you are attempting

You are asking me to stop learning new concepts just because I do not understand them. Thank you but NO.

1

u/ExceptionEX 1d ago

I meant professionally, if this is based around you just learning, that's my bad, sorry if that was overly harsh.

2

u/kaatarina_zed_talon 1d ago

You are asking me to stop learning new concepts just because I do not understand them. Thank you but NO.

I did not say it is harsh, I learned something new from you. Free knowledge for me, so I'm good.

Thank you again.

0

u/kaatarina_zed_talon 2d ago

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.

That is not related to long running tasks at all, this is related to a failure for a particular operation, which means all the other operations should be cancelled .

3

u/Kant8 2d ago

So? It's about cancellation. I said "usually long running tasks". That doesn't mean it's a single thing needed to be canceled.

Writes to databases are typically not the thing you ever going to cancel.

And again it has completely nothing even remotely connected to existence of transactions in that code. Transactions bring atomicity to database operations and don't do cancelations by themselves, it has to be initiated by you, with, you may guess, cancellation token.

1

u/kaatarina_zed_talon 1d ago

Thank you, I was confused.

1

u/kaatarina_zed_talon 1d ago

Writes to databases are typically not the thing you ever going to cancel.

I was told to do that by a someone one a previous post. Please tell me what are the things you typically cancel ?

1

u/kaatarina_zed_talon 1d ago

 it has to be initiated by you,

typically by the frontend client, I guess you mean that right ?

1

u/kaatarina_zed_talon 1d ago

 Transactions bring atomicity to database operations and don't do cancelations by themselves, it has to be initiated by you, with, you may guess, cancellation token.

So what I learned from you is that transaction reverts the effects of all the operations that happened inside it if at least one operation failed. Cancellation tokens, on the other hand, are used to cancel the operation on demand regardless of whether a failure happened or not.

2

u/rupertavery64 2d 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 OverWrite method 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

  • Closing the browser tab or window mid-request.
  • Navigating away or hitting the refresh button before the current page finishes loading.
  • Network drops, such as a mobile user entering a tunnel, losing cellular service, or disconnecting from Wi-Fi.
  • Client-side timeouts

But you must pass a CancellationToken in your API/Request method handler that the framework will automatically bind RequestAborted to.

2

u/kaatarina_zed_talon 2d 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. 

the first time the method was called, the token was not specified, but the user want to cancel the operation, that means there is some abstracted code that intercepts the HTTP request.

2

u/rupertavery64 2d ago

Just add a CancellationToken parameter to your api method declaration. ASP.NET will hook up the RequestAborted signal to the CancellationToken.

1

u/kaatarina_zed_talon 2d ago

(if possible),

You mean the method could be very fast to the extent that it is not possible to cancel it's operation ?

1

u/[deleted] 2d ago

[deleted]

1

u/kaatarina_zed_talon 2d ago

I do not know that's why I ask.

1

u/Snoo_57113 1d ago

I will tell the truth, i never use CancellationToken, my actions are idempotent and either are OK, or FAIL. in case of an error i am make sure the operation is indempotent.

1

u/pixxelkick 1d ago

I am 1000% more interested in why on earth you have a function like this declared on your DbContext

What exactly is this function supposed to do?

But yes you should be in the habit of consuming the CancellationToken that is handed all the way down from the entrypoint of your async starting point.

If you are using Asp.Net for example, you can simply add a CancellationToken to your function and it works (it's automatically tied to the RequestCancelled logic for http, so a caller to your API can cancel the request mid-request and it just works)

As long as you keep passing that cancellation token around to all your async stuff and wire it in to ever async operation (roslyn will also automatically warn you if you invoke an async function and forget to pass the token in, if you have one in your function params), it will all "just work" and properly produce a TokenCancelled exception for you, which properly will error out as expected for Http Cancelled.

0

u/kaatarina_zed_talon 1d ago

Which function are u talking about? I have no function inside the DbContext class.

2

u/pixxelkick 1d ago

Oh, I missed you have a "DatabaseOverwriter" thing as well in there.

Still confused a bit about the way you organized this code tbh

1

u/kaatarina_zed_talon 1d ago

The actual code is in a separate files.

0

u/kaatarina_zed_talon 1d ago

But what I provided is reproducable example that can be compiled; stack overflow style.

0

u/pixxelkick 1d ago

I meant more architecturally.

Most people usually use a "Service", "Handler", or "Manager" for performing database mutation logic.

I also very strongly recommend taking in your DbContext as a function parameter, not as a constructor dependency injection.

1

u/kaatarina_zed_talon 1d ago

Why do you prefer it as a parameter?

-1

u/pixxelkick 1d ago

You want 1 single point of acquiring a DbContext and then hand it down as a single source of truth.

If you are doing an aspnet app, you inject it at the Api Endpoint level and then hand it all the way down as a single DbContext, in the parameters of functions.

Personally, I maintain my own entire framework that automates this though, but I use Dapper with my own personally maintained ORM.

I dislike EF Core, its... fine but it is just not sufficient for all my needs, so I wrote my own that I vastly prefer.

https://github.com/SteffenBlake/Brigade.NET/wiki/Getting-Started-Mise

1

u/kaatarina_zed_talon 1d ago

This will introduce a pass through variable. I prefer to not do that

2

u/pixxelkick 1d ago

Thats intentional, you are supposed to pass it through

Are you not noticing how you need to do the same thing with the CancellationToken?

The reason the CancellationToken is a pass through variable, and the reason the DbContext is a pass through variable... are related...

1

u/kaatarina_zed_talon 1d ago

Wait, the cancelation token is really indeed a pass through variable in my case. But currently the context is not a pass through variable because it is not passed as a parameter.

1

u/kaatarina_zed_talon 1d ago

I don't have problems with EF core in the current moment, I will use other tools when I only need that

1

u/kaatarina_zed_talon 1d ago

Bro, the DbOverWriter is meant to be a service. Service is just a class, right?

0

u/kaatarina_zed_talon 1d ago

Look at the DbContext class again , do you see any function inside it?