Reputation: 7277
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
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
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
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