Caramiriel
Caramiriel

Reputation: 7277

Task cancellation, when?

For an implementation of an API I'm making I want users to be able to cancel their tasks (Task). I figured out I would need a CancellationTokenSource so I could create a CancellationToken from the former. Now that I'm at the point of handling the cancellation request (IsCancellationRequested/ThrowIfCancellationRequested) I'm a bit confused. What would be the proper time to check/do this?

For example (fictitious):

async Task<int> DoStuff(int number, CancellationToken token) {
    // 1. Here, callee-site?
    token.ThrowIfCancellationRequested();

    var resultTask1 = _database.GetSomeDataFromDatabase(token); // 2. Inside this method?
    var resultTask2 = _service.SomeRestCall(token); // 2. Inside this?

    // 3. Here, before the tasks return?
    token.ThrowIfCancellationRequested();
    var result1 = await resultTask1;
    var result2 = await resultTask2;

    // 4. In memory processing, here?
    foreach(var item in result1)
    {
        // ...
    }

    foreach(var item in result2)
    {
        // 1. Here?
        token.ThrowIfCancellationRequested();
        await _fileSystem.Save(fileName, item, token); // 5. Inside this?
    }

    // 6. Here probably doesn't make sense though, the result is already retrieved?
    return 123;
}

What is best-practice for task cancellation?

Upvotes: 1

Views: 217

Answers (3)

Stephen Cleary
Stephen Cleary

Reputation: 457217

What would be the proper time to check/do this?

For CPU-bound methods, it would be proper to call CancellationToken.ThrowIfCancellationRequested periodically. E.g., if you had a tight processing loop, you may want to call it every few hundred iterations or so.

However, it sounds like your situation is all I/O-bound (naturally asynchronous). In this case, it's best to just pass the token down. Higher-level asynchronous methods can just pass the token along. At the lowest level, if the API does not support CancellationToken directly, then it's generally best to use CancellationToken.Register to implement a true cancellation of the asynchronous operation. Of course, if the lowest-level API does support CancellationToken directly, then just pass it through.

Upvotes: 4

vendettamit
vendettamit

Reputation: 14687

There's nothing called "Best-Practice". Usage of it may differ in different scenarios.

Basically the ThrowIfCancellationRequested() is used to prevent running any extra line of code when Cancellation is asked. Think of it as below type check:

if (token.IsCancellationRequested) 
    throw new OperationCanceledException(token); // break the processing immediately

Now its up to you to decide which code section should stop execution when a cancellation is requested. As @Jgauffin suggested LongRunning code sections would be a great place to put such checks. Also if you have multiple areas in your e.g. in given sample 2 for loops then you can use in both to avoid further processing once cancellation is asked.

Upvotes: 0

jgauffin
jgauffin

Reputation: 101194

Cancelling is typically done when waiting for operations that take time. For instance waiting on IO operations or doing CPU-based calculations.

In your example there is really no need to cancel at #1, #2 and #6 since they just blaze through without a delay.

It would however make sense to check for cancellation in #4 if the list is large or if the computation takes time.

Finally #5 is a winner since IO isn't fast. However, doesn't _fileSystem.Save throw TaskCancelledException? In that case it's already been taken care of.

Do note that by taking a cancellation token you also say that things are revertable. If not, be crystal clear in the documentation that cancelling will just abort those computations that have not been saved yet.

Upvotes: 3

Related Questions