how cancellation token stop continue with? - c#

I have a Thread with this code:
foreach (string file in allDirectoriesFiles)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
// ... some operation...
}
// ... some operation 2...
notice that I use return instead of break, because if I stop the thread the some operation 2 must not be executed.
I also notice that in this manner the .ContinueWith it is not executed.
Well, but that is just a "return" function. How can C# know that I'm returning due to the tokenSource.Cancel() instead of a usual return?

You should not create the continuation with the same cancellation token or else the continuation will also be cancelled, even before it is executed. Create the continuation with a different cancellation token (from a different cancellation source) or simply create it without a cancellation token if you always want the cancellation to execute.

A CancellationToken is meant to propagate the cancelling to the whole set of actions at once. Once you Cancel() its source, the other continuations won't be called.

Related

Execution of ContinueWith when the antecedent Task is in canceled state and usage of async delegate inside of ContinueWith

The code discussed here is written in C# and executed with .netcore 3.1
I have the following piece of code, which starts a workload in the background without awaiting for it to complete (fire and forget):
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
try
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
}
finally
{
// here I need to release the resource. Releasing this resource is important and must be done as soon as possible
await resource.DisposeAsync();
}
}, token);
}
There are three important points:
the background work is started in a fire and forget manner. I'm not interested in awaiting its completion
the provided cancellation token is important and the background work must listed to incoming cancellation requests
the provided resource (IAsyncDisposable) must be released as soon as possible, regardless of the outcome of the background work. In order to release the resource a call to DisposeAsync is required.
The problem with this code is that the cancellation token is passed to Task.Run invokation. If the token is canceled before the execution of the async delegate starts, the async delegate is never executed and so the finally block is never executed. By doing so the requirement of releasing the IAsyncDisposable resource is not met (basically, DisposeAsync is never called).
The simplest way to solve this issue is not providing the cancellation token when Task.Run is invoked. That way, the async delegate is always executed and so the finally block is executed too. The code inside the async delegate listens to cancellation requests, so the requirement of cancel the execution is met too:
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
try
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
}
finally
{
// here I need to release the resource. Releasing this resource is important and must be done as soon as possible
await resource.DisposeAsync();
}
}, CancellationToken.None);
}
I'm asking myself whether the release of the IAsyncDisposable resource should, instead, be delegated to a continuation task. The code refactored by using this approach is the following:
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
},
token).ContinueWith(async _ =>
{
// release the IAsyncDisposable resource here, afte the completion of the antecedent task and regardless
// of the antecedent task actual state
await resource.DisposeAsync();
});
}
I'm not really familiar with ContinueWith gotchas, so my questions are the following:
do I have the guarantee that the continuation is always executed, even if the cancellation token is canceled before the execution of the antecedent task starts ?
is there any issue in providing an async delegate to the invokation of ContinueWith ? Is the execution of the async delegate fully completed as expected ?
What is the best approach ? Passing CancellationToken.None to the invokation of Task.Run, or relying on the continuation by using ContinueWith ?
IMPORTANT NOTE: I know that using Task.Run is not the best approach in a server application (more on that can be found here), so there are probably much better ways of designing my overall architecture. I posted this question to better understanding the actual behavior of ContinueWith, because I'm not really familiar with its usage (in modern .NET code it is largely replaced by the usage of async await).
You could consider using the await using statement, that handles the asynchronous disposal of the resource automatically:
public async void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
await using var _ = resource;
try
{
await Task.Run(async () =>
{
await Task.Delay(1500, token);
}, token);
} catch (OperationCanceledException) { }
}
I also converted your fire-and-forget task to an async void (aka fire-and-crash) method. In case the unthinkable happens and your code has a bug, instead of the app continue running with an unobserved exception having occurred, resulting possibly to corrupted application state, the whole app will crash, forcing you to fix the bug ASAP.
But honestly creating a disposable resource in one method and disposing it in another is a smelly design. Ideally the method that created the resource should be responsible for disposing it finally.
I think Theodor has a great answer; I'm just going to answer some of your other questions:
do I have the guarantee that the continuation is always executed, even if the cancellation token is canceled before the execution of the antecedent task starts ?
ContinueWith will execute its delegate even of the antecedent task is already completed. In this specific case, there is no "guarantee" simply because of the nature of fire-and-forget.
is there any issue in providing an async delegate to the invokation of ContinueWith ?
ContinueWith is not async-aware, so the return type of ContinueWith is surprising for most developers. Since your code discards the return type, that's not a concern here.
Is the execution of the async delegate fully completed as expected ?
In this case, most likely, but it really depends on what "expected" means. Like all other fire-and-forget code, you can't guarantee completion. ContinueWith has an additional wrinkle: it executes its delegate using a TaskScheduler, and the default TaskScheduler is not TaskScheduler.Default but is actually TaskScheduler.Current. So I always recommend passing an explicit TaskScheduler for clarity if you really need to use ContinueWith.
What is the best approach ? Passing CancellationToken.None to the invokation of Task.Run, or relying on the continuation by using ContinueWith ?
Just drop the second argument to Task.Run.
I'll go further than that: Task.Run probably shouldn't even take a CancellationToken. I have yet to see a scenario where it's useful. I suspect the CancellationToken part of the API was copied from TaskFactory.StartNew (where it is rarely useful), but since Task.Run always uses TaskScheduler.Default, providing a CancellationToken is not useful in practice.
P.S. I recently wrote a short series on the proper solution for fire-and-forget on ASP.NET.

Windows Service Task - Polling Cancellation

I am writing a windows service and found an example which suggests writing a polling windows services as follows:
private void Poll()
{
CancellationToken cancellationPoll = ctsPoll.Token;
while (!cancellationPoll.WaitHandle.WaitOne(tsInterval))
{
PollDatabase();
// Occasionally check the cancellation state.
if (cancellationPoll.IsCancellationRequested)
{
break;
}
}
}
I am a little confused when it comes to the cancellation and if i need both the cancellationPoll.WaitHandle.WaitOne() and cancellationPoll.IsCancellationRequested or are they doing the same thing and only one is required?
The !cancellationPoll.WaitHandle.WaitOne(tsInterval) is there to ensure the polling interval so you will have at least tsIntetval between polling(+ the operation duration):
--tsInterval--|--operation--|--tsInterval--|...
If you look at the documentation for CancellationToken.WaitHandle it says the following:
A WaitHandle that is signaled when the token is canceled.
So in your case the operation cancellationPoll.IsCancellationRequested is sufficient because you don't have anything after it. But imagine the situation like this:
while (!cancellationPoll.WaitHandle.WaitOne(tsInterval))
{
//long operation A
if (cancellationPoll.IsCancellationRequested)
{
break;
}
//long operation B
if (cancellationPoll.IsCancellationRequested)
{
break;
}
//long operation C
}
In this case it makes sense to occasionally check the cancellation state to avoid running long operation...
The waiting of WaitHanlde is redundant here as from the result standpoint it does the same as IsCancellationRequested - indicates that cancellation is requested (but does it in slightly different way). So for your case you can choose single method: either WaitHandle or IsCancellationRequested. But please keep in mind that WaitHandle is IDisposable and requires disposing the associated CancellationTokenSource. If you choose to use IsCancellationRequested don't forget to add a call which is supposed to reschedule thread such as Thread.Sleep in order not to over-utilize the CPU resources.
One of scenario when WaitHanlde can be applied is when you need to wait for a handle and would like to introdece cancellation semantic to this wait:
WaitHandle.WaitAny(new [] { handleToWait, cancellationHandle });
The !cancellationPoll.WaitHandle.WaitOne(tsInterval) is needed so that you do not wait the whole time. WaitOne(tsInterval) will either return because the token recevied a singnal to cancel or because the time is run out. If the token recieved a signal to cancel WaitOne(tsInterval) will return true and so end the loop.
For example, if you would do something like:
while(true)
{
// long operation
if (cancellationPoll.IsCancellationRequested)
{
break;
}
Thread.Sleep(tsInterval);
}
if then the cancellation is reqested while the thread is blocked by Thread.Sleep() the whole operation would not know that an cancellation is reqested not until Thread.Sleep() is finished and the next loop run has come to the if statement.

How to handle tasks that are canceled without triggering a cancellation itself?

I'm trying to get tasks in C# to work for a specific use case but I'm not understanding how task continuation options affect the flow of tasks.
What I'm trying to do is get a series of tasks chained together with ContinueWith. This will look something like this:
A -> B -> C -> D
However, I want to include the option to short-circuit this in the event an error, so it should look like this:
A -> B -> C -> D -> X
So I put "OnlyOnRanToCompletion" as the task continuation option for each of the ContinueWith functions. Then, to catch the cancellation and return an error, I put a final task at the end of the chain with the task continuation option set to "OnlyOnCanceled".
The problem is that when this last block is hit, the continuation option is not met and the task then gets set to cancelled even if the original series of tasks was never cancelled.
What I want to happen is have A through D run, and if one of them results in a cancellation, skip the rest and run X. If A through D complete, the task shouldn't cancel. The solution needs to support an arbitrary number of continuations and will be created using LINQ.Expressions, so using async/await is probably not going to fly unless it's done creatively.
Some sample code that exhibits this is:
var cts = new CancellationTokenSource();
var token = cts.Token;
var t = Task.FromResult(1)
.ContinueWith(
x => x.Result + 1,
token,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default)
.ContinueWith(
x => x.Result + 1,
token,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default)
.ContinueWith(
x => -1,
token,
TaskContinuationOptions.OnlyOnCanceled,
TaskScheduler.Default);
The expected behavior here would be to return 3, and the status not completed.
The actual result is that the task is cancelled.
How do I do this?
Also, I can't use async because my goal is to piggyback off TPL inside of something compiled from LINQ.Expressions so that it can evaluate asynchronously and handle errors at the end without throwing any exceptions.
Figured it out - to get the last continuation to run regardless of whether or not the previous continuations completed and without setting the status to canceled do this:
Change the continuation option of the last continuation to TaskContinuation.None so that it always runs, so it won't cancel if it gets here with a status of completed.
Don't pass in a cancellation token to the last continuation because passing in a cancellation token that has been cancelled seems to have the effect of causing the continuation to cancel if it would have otherwise run without the token.
See the remarks for ContinueWith for an explanation of this behavior:
The returned Task will not be scheduled for execution until the current task has completed. If the criteria specified through the continuationOptions parameter are not met, the continuation task will be canceled instead of scheduled.
Since the criteria for your last ContinueWith call weren't met, the Task returned from that call was cancelled.

C# Task.Factory.CancellationToken

My question is about task cancellation. I have to poll the token.IsCancellationRequested to detect a cancellation. I call cts.Cancel(); in a WindowsForm Buttonmethod.
Questions:
If I hit the Button is the CancelRequest stored? Or do I have to be lucky, that to same time when I press my Button the code is at the position if (token.IsCancellationRequested)?
Is it possible to cancel my Task with for-loop by event?
Code Example:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task t1 = Task.Factory.StartNew(() =>
{
// Do syncronius work
for(int i=0; i<1000;++i)
{
DoSyncWork(i);
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancelled");
break;
}
Thread.Sleep(1000);
}
});
The cancellation request is a one time thing. Once a token source is canceled it can never be un-canceled so all derived tokens will have IsCancellationRequested always return true.
Yes it is possible, but for a for loop I don't think it is a better way. The way you use a event is you pass the callback to the CancellationToken.Register method and the callback is your event. I leave it to you how you would make a Action delegate cancel the for loop.
A few things with your code that you did not bring up:
You should never call Task.Factory.StartNew without passing in TaskScheduler, if you don't you could cause your code in the StartNew to run on the UI thread when you expect it to be on a background thread. Either use Task.Run or make sure you pass in a scheduler (TaskScheduler.Default is the one Task.Run( uses to always run on the background thread, TaskScheduler.Current is the one that is used when you don't pass anything in and is the one that can cause stuff to run on the UI thread).
If you pass the token in to the factory (or to Task.Run() then use token.ThrowIfCancellationRequested() this will cause the task to enter the Canceled state instead of the Completed state (if you forget to pass it to the factory it will enter the Faulted state), this can be useful for when you need to know when the task finished or not when you are awaiting.
The cancellation is "stored". If you call Cancel() on your CancellationTokenSource instance, the IsCancellationRequested property of the CancellationToken will be true for the rest of its existence.
As I understand it, you want to break your for loop by an event? I don't know how this should look like. The control flow of a for loop is straight forward, no event could break that. But you can use the token in the for loop's header:
for(int i=0; i<1000 && !token.IsCancellationRequested; ++i)
{
...
}
// output log if cancelled
if (token.IsCancellationRequested) Console.WriteLine(...);
if it's that what you want.
The usual implementation for cancelling out with a cancellation token is to throw the OperationCanceledException, by using the tokens .ThrowIfCancellationRequested. This allows you to catch a cancelled operation and get out of operating for however deep you are in the stack and know that it was cancelled.
For your first question, as soon as the token has been cancelled, cancellation will be requested and when you come back around in the loop the if block would be true that you have. Instead of that though I would just use token.ThrowIfCancellationRequested, and catch the specific OperationCanceledException and do any logging you want.
Second question, you can register a cancellation from anything that can access to your cancellationtokensource by calling cancel. So any event that is able to access the cancellationtokensource you could call it's cancellation event. I will often put a tokensource as an instance variable on a form that should support cancellation so that a "cancel" button or some other event that causes cancellation can call on the cancel method for the cts.
Example of one way I'll set up a form with a token:
public class MyForm
{
private CancellationTokenSource _cts;
private void Cancel()
{
if (_cts != null) {
_cts.Cancel();
}
}
}

How to stop all tasks when one is finished in c#

I have different tasks to read from different files and find a word into them. I have put them into a task array which I start with waitAny method as following :
foreach (string file in filesList)
{
files[i] = Task.Factory.StartNew(() =>
{
mySearch.Invoke(file);
});
i++;
}
System.Threading.Tasks.Task.WaitAny(files);
I would like to stop all other tasks as soon as one of the tasks finishes (it finishes when it founds the word). For the moment, with waitany, i can know when one tasks finishes, but I don't know how I could know which one has finished and how to stop other tasks.
What would be the best way to achieve this ?
You can use single CancellationToken which all tasks will share. Inside mySearch.Invoke method verify value of token.IsCancellationRequested to cancel task. When some task will be finished cancel others via CancellationTokenSource.Cancel().
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
foreach (string file in filesList)
{
// pass cancellation token to your task
files[i] = Task.Factory.StartNew(() => mySearch.Invoke(file, token), token);
i++;
}
Task.WaitAny(files);
tokenSource.Cancel();
BTW you can force token to throw OperationCanceledException when source is canceled by calling token.ThrowIfCancellationRequested()
When creating a Task you can pass a CancelationToken. Set this token when one of the tasks finishes.
This will cause remaining tasks with this token to not execute. Running tasks can receive a OperationCanceledException and stop too.
I highly suggest reading How do I cancel non-cancelable async operations? by Stephen Toub. Essentially what you need to do is cancel all of these tasks, but currently you have no mechanism to cancel them.
The ideal approach would be to create a CancellationTokenSource before the foreach, pass the CancellationToken from that source to each of the child tasks, check that token periodically and stop doing work when you notice it's indicated cancellation. You can then cancel the token source in the WhenAny continuation.
If that's not an option you need to decide if it's important to actually stop the tasks (which, really, just can't be done) or if you just need to continue on with your code without waiting for them to finish (that's easy enough to do).

Categories

Resources