What is the use of CancellationToken's IsCancellationRequested property? Consider below code
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
Console.WriteLine("Press Enter to Start.\nAgain Press enter to finish.");
Console.ReadLine();
Task t = new Task(() =>
{
int i = 0;
while (true)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Task Cancel requested");
break;
}
Console.WriteLine(i++);
}
}, token);
t.Start();
// wait for input before exiting
Console.ReadLine();
tokenSource.Cancel();
if(t.Status==TaskStatus.Canceled)
Console.WriteLine("Task was cancelled");
else
Console.WriteLine("Task completed");
}
I find that on rare occasions code inside if block doesn't run. If so what is the use of polling to see if cancellation is requested?
The problem with your code is that you don't wait for the Task to finish. So, what can happen is this:
You call Cancel().
You check Status, which returns Running.
Confusingly, you write “Task completed” when the Task is still running.
Main() completes, application exits.
(At this point, IsCancellationRequested would be checked from the background thread. But that never happens, since the application already exited.)
To fix that, add t.Wait() after you call Cancel().
But that still won't fix your program completely. You need to tell the Task that it was canceled. And you do that by throwing OperationCanceledException that contains the CancellationToken (the usual way to do that is to call ThrowIfCancellationRequested()).
One issue with that is that Wait()ing on a Task that was canceled will throw an exception, so you will have to catch that.
Related
I have a simple Console application with 2 running threads.
The first thread is measuring some values, and the second thread looks for user input and performs some mouse movements.
while (true)
{
if (Input.IsKeyDown(VC_L))
{
Mouse.Move(300, 500);
Thread.Sleep(thread1_delay);
Mouse.Move(670, 300);
Thread.Sleep(thread1_delay);
Mouse.Move(870, 700);
Thread.Sleep(thread1_delay);
}
}
The problem is I want to stop the second thread as soon as I get another key as input. But it's not working since the thread is still sleeping and doesn't react.
Just use a CancellationToken and be done with it
Propagates notification that operations should be canceled.
Example
public static async Task DoFunkyStuff(CancellationToken token)
{
// a logical escape for the loop
while (!token.IsCancellationRequested)
{
try
{
Console.WriteLine("Waiting");
await Task.Delay(1000, token);
}
catch (OperationCanceledException e)
{
Console.WriteLine("Task Cancelled");
}
}
Console.WriteLine("Finished");
}
Usage
static async Task Main(string[] args)
{
var ts = new CancellationTokenSource();
Console.WriteLine("Press key to cancel tasks");
var task = DoFunkyStuff(ts.Token);
// user input
Console.ReadKey();
Console.WriteLine("Cancelling token");
// this is how to cancel
ts.Cancel();
// just to prove the task has been cancelled
await task;
// because i can
Console.WriteLine("Elvis has left the building");
Console.ReadKey();
}
Results
Press key to cancel tasks
Waiting
Waiting
Waiting
Waiting
Waiting
Cancelling token
Task Cancelled
Finished
Elvis has left the building
The second thread should check for a Boolean value when it wakes up. You should set this value to false when your condition is met. Now when the second thread wakes up it will complete it's execution.
According to the documentation:
A dataflow block is considered completed when it is not currently processing a message and when it has guaranteed that it will not process any more messages.
This behavior is not ideal in my case. I want to be able to cancel the job at any time, but the processing of each individual action takes a long time. So when I cancel the token, the effect is not immediate. I must wait for the currently processed item to complete. I have no way to cancel the actions directly, because the API I use is not cancelable. Can I do anything to make the block ignore the currently running action, and complete instantly?
Here is an example that demonstrates my problem. The token is canceled after 500 msec, and the duration of each action is 1000 msec:
static async Task Main()
{
var cts = new CancellationTokenSource(500);
var block = new ActionBlock<int>(async x =>
{
await Task.Delay(1000);
}, new ExecutionDataflowBlockOptions() { CancellationToken = cts.Token });
block.Post(1); // I must wait for this one to complete
block.Post(2); // This one is ignored
block.Complete();
var stopwatch = Stopwatch.StartNew();
try
{
await block.Completion;
}
catch (OperationCanceledException)
{
Console.WriteLine($"Canceled after {stopwatch.ElapsedMilliseconds} msec");
}
}
Output:
Canceled after 1035 msec
The desired output would be a cancellation after ~500 msec.
Based on this excerpt from your comment...:
What I want to happen in case of a cancellation request is to ignore the currently running workitem. I don't care about it any more, so why I have to wait for it?
...and assuming you are truly OK with leaving the Task running, you can simply wrap the job you wish to call inside another Task which will constantly poll for cancellation or completion, and cancel that Task instead. Take a look at the following "proof-of-concept" code that wraps a "long-running" task inside another Task "tasked" with constantly polling the wrapped task for completion, and a CancellationToken for cancellation (completely "spur-of-the-moment" status, you will want to re-adapt it a bit of course):
public class LongRunningTaskSource
{
public Task LongRunning(int milliseconds)
{
return Task.Run(() =>
{
Console.WriteLine("Starting long running task");
Thread.Sleep(3000);
Console.WriteLine("Finished long running task");
});
}
public Task LongRunningTaskWrapper(int milliseconds, CancellationToken token)
{
Task task = LongRunning(milliseconds);
Task wrapperTask = Task.Run(() =>
{
while (true)
{
//Check for completion (you could, of course, do different things
//depending on whether it is faulted or completed).
if (!(task.Status == TaskStatus.Running))
break;
//Check for cancellation.
if (token.IsCancellationRequested)
{
Console.WriteLine("Aborting Task.");
token.ThrowIfCancellationRequested();
}
}
}, token);
return wrapperTask;
}
}
Using the following code:
static void Main()
{
LongRunningTaskSource longRunning = new LongRunningTaskSource();
CancellationTokenSource cts = new CancellationTokenSource(1500);
Task task = longRunning.LongRunningTaskWrapper(3000, cts.Token);
//Sleep long enough to let things roll on their own.
Thread.Sleep(5000);
Console.WriteLine("Ended Main");
}
...produces the following output:
Starting long running task
Aborting Task.
Exception thrown: 'System.OperationCanceledException' in mscorlib.dll
Finished long running task
Ended Main
The wrapped Task obviously completes in its own good time. If you don't have a problem with that, which is often not the case, hopefully, this should fit your needs.
As a supplementary example, running the following code (letting the wrapped Task finish before time-out):
static void Main()
{
LongRunningTaskSource longRunning = new LongRunningTaskSource();
CancellationTokenSource cts = new CancellationTokenSource(3000);
Task task = longRunning.LongRunningTaskWrapper(1500, cts.Token);
//Sleep long enough to let things roll on their own.
Thread.Sleep(5000);
Console.WriteLine("Ended Main");
}
...produces the following output:
Starting long running task
Finished long running task
Ended Main
So the task started and finished before timeout and nothing had to be cancelled. Of course nothing is blocked while waiting. As you probably already know, of course, if you know what is being used behind the scenes in the long-running code, it would be good to clean up if necessary.
Hopefully, you can adapt this example to pass something like this to your ActionBlock.
Disclaimer & Notes
I am not familiar with the TPL Dataflow library, so this is just a workaround, of course. Also, if all you have is, for example, a synchronous method call that you do not have any influence on at all, then you will obviously need two tasks. One wrapper task to wrap the synchronous call and another one to wrap the wrapper task to include continuous status polling and cancellation checks.
what's expected in this case, is that if the user cancels the task by hitting enter, the other task hooked by ContinueWith will run, but it's not the case, as per an AggregateException keeps thrown despite the explicit handling in the ContinueWith which is apparently not being executed.
any clarification on the below please?
class Program
{
static void Main(string[] args)
{
CancellationTokenSource tokensource = new CancellationTokenSource();
CancellationToken token = tokensource.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
t.Exception.Handle((e) => true);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
Console.WriteLine("Press any key to cancel");
Console.ReadLine();
tokensource.Cancel();
task.Wait();
}
}
Let's start with a few facts:
When you pass a CancellationToken as a parameter for Task.Run it only has an effect if it's cancelled before the task started running. If the task is already running it will not be canceled.
To get a task canceled after it has started running, you need to use CancellationToken.ThrowIfCancellationRequested, not CancellationToken.IsCancellationRequested.
If a task is canceled, its Exception property doesn't hold any exceptions and is null.
If a continuation task does not run for some reason, that means it was canceled.
A task contains exceptions from itself + all its child tasks (hence, AggregateException).
So this is what happens in your code:
The task starts running, because the token is not canceled. It will run until the token gets canceled. After it will end the continuation will not run because it only runs when the preceding task is canceled, and it hasn't been. When you Wait the task it will throw an AggregateException with a TaskCanceledException because the continuation was canceled (if you would remove that continuation the exception will go away).
Solution:
You need to fix the task so it would actually be canceled, and remove (or null check) the exception handling because there is no exception:
var task = Task.Run(new Action(() =>
{
while (true)
{
token.ThrowIfCancellationRequested();
Console.Write("*");
Thread.Sleep(1000);
}
}), token).ContinueWith(
t => Console.WriteLine("You have canceled the task"),
TaskContinuationOptions.OnlyOnCanceled);
If you pass the token as the second parameter, the task won't continue on nicely because it's really been cancelled. Instead it throws an OperationCanceledException which gets wrapped in an AggregateException. This is entirely expected. Now, if you did NOT pass the token to the task constructor then you'd see the behaviour you'd expect because because you'd only be using the token as a flag for exiting the while loop. In that case you're not really cancelling the task, you're exiting the while loop and completing the task normally.
This question already has answers here:
How to cancel a Task in await?
(4 answers)
Closed 3 years ago.
OK, my questions is really simple. Why this code does not throw TaskCancelledException?
static void Main()
{
var v = Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}, new CancellationTokenSource(500).Token).Result;
Console.WriteLine(v); // this outputs 10 - instead of throwing error.
Console.Read();
}
But this one works
static void Main()
{
var v = Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}, new CancellationToken(true).Token).Result;
Console.WriteLine(v); // this one throws
Console.Read();
}
Cancellation in Managed Threads:
Cancellation is cooperative and is not forced on the listener. The listener determines how to gracefully terminate in response to a cancellation request.
You didn't write any code inside your Task.Run method to access your CancellationToken and to implement cancellation - so you effectively ignored the request for cancellation and ran to completion.
There is a difference in canceling a running task, and a task scheduled to run.
After the call to the Task.Run method, the task is only scheduled, and probably have not been executed yet.
When you use the Task.Run(..., CancellationToken) family of overloads with cancellation support, the cancellation token is checked when the task is about to run. If the cancellation token has IsCancellationRequested set to true at this time, an exception of the type TaskCanceledException is thrown.
If the task is already running, it is the task's responsibility to call the ThrowIfCancellationRequested method, or just throw the OperationCanceledException.
According to MSDN, it's just a convenience method for the following:
if (token.IsCancellationRequested)
throw new OperationCanceledException(token);
Note the different kind of exception used in this two cases:
catch (TaskCanceledException ex)
{
// Task was canceled before running.
}
catch (OperationCanceledException ex)
{
// Task was canceled while running.
}
Also note that TaskCanceledException derives from OperationCanceledException, so you can just have one catch clause for the OperationCanceledException type:
catch (OperationCanceledException ex)
{
if (ex is TaskCanceledException)
// Task was canceled before running.
// Task was canceled while running.
}
In the first variant of your code, you're not doing anything to manage the cancellation token.
For example, you're not checking whether token.IsCancellationRequested returns true (and then throwing an exception) or calling ThrowIfCancellationRequested() from your CancellationToken object.
Also, the Task.Run overload you used checks whether the token is already canceled or not when the task is about to start and your code states that token will report cancellation after 500 ms.
So, your code is just ignoring the cancellation request and that's why the task ran to completion.
You should do something like this:
void Main()
{
var ct = new CancellationTokenSource(500).Token;
var v =
Task.Run(() =>
{
Thread.Sleep(1000);
ct.ThrowIfCancellationRequested();
return 10;
}, ct).Result;
Console.WriteLine(v); //now a TaskCanceledException is thrown.
Console.Read();
}
or this, without passing the token, as others already noted:
void Main()
{
var ct = new CancellationTokenSource(500).Token;
ct.ThrowIfCancellationRequested();
var v =
Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}).Result;
Console.WriteLine(v); //now a TaskCanceledException is thrown.
Console.Read();
}
The second variant of your code works, because you're already initializing a token with a Canceled state set to true. Indeed, as stated here:
If canceled is true, both CanBeCanceled and IsCancellationRequested will be true
the cancellation has already been requested and then the exception TaskCanceledException will be immediately thrown, without actually starting the task.
Another implementation using Task.Delay with token instead it Thread.Sleep.
static void Main(string[] args)
{
var task = GetValueWithTimeout(1000);
Console.WriteLine(task.Result);
Console.ReadLine();
}
static async Task<int> GetValueWithTimeout(int milliseconds)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(milliseconds);
token.ThrowIfCancellationRequested();
var workerTask = Task.Run(async () =>
{
await Task.Delay(3500, token);
return 10;
}, token);
try
{
return await workerTask;
}
catch (OperationCanceledException )
{
return 0;
}
}
I tried to run a simple example on the cancellation of a task like the one below
CancellationTokenSource tokenSource2 = new CancellationTokenSource();
CancellationToken token2 = tokenSource2.Token;
Task task2 = new Task(() =>
{
for (int i = 0; i < int.MaxValue; i++)
{
token2.ThrowIfCancellationRequested();
Thread.Sleep(100);
Console.WriteLine("Task 2 - Int value {0}", i);
}
}, token2);
task2.Start();
Console.WriteLine("Press any key to cancel the task");
Console.ReadLine();
tokenSource2.Cancel();
Console.WriteLine("Task 2 cancelled? {0}", task2.IsCanceled);
I expected that Console.WriteLine("Task 2 cancelled? {0}", task2.IsCanceled); would print **"Task 2 cancelled? True"**, but it printed "False".
Do you know what happened? Is that the expected behaviour? Thanks.
EDIT: to ensure that the task hasn't completed before the cancellation request is called. I added the Console.ReadLine().
First, maybe you misunderstand what IsCanceled means? It doesn't mean “this Task is pending cancellation, so it should complete shortly”, it means “this Task has been canceled, it is now complete”.
If you didn't misunderstand that, think about what exactly the sequence of events is. What happens is this:
ThrowIfCancellationRequested() is called, but the token wasn't canceled yet, so it doesn't throw.
Thread.Sleep() is called, so the thread running the Task sleeps.
Cancel() is called.
IsCanceled is checked. The code in the Task didn't have a chance to realize that the token was canceled, so it's still running, so IsCanceled returns false.
ThrowIfCancellationRequested() is called again, this time it throws, which actually cancels the Task.
This is why ISCanceled is returning false to you. If you want it to return true, you could add something like Thread.Sleep(150) before checking IsCanceled, or, even better, actually wait for the Task to complete.
The Task ended before you call for cancellation, take a look at the following below this may help to shed some more light on how to resolve your issue
By reading from here http://social.msdn.microsoft.com/Forums/da-DK/parallelextensions/thread/9f88132a-f8bd-4885-ab63-645d7b6c2127 it seems that the token is used to cancel the task BEFORE the task is "really" started, but after it has been queued.
It's more a way to cancel a task that's scheduled to occur, but not started yet. Once the task is running, the only way to cancel it is cooperatively via your own checking within the method. Without this, you'd have to always start the task, then check it internally, which would add a lot of extra, unnecessary overhead
You can even read it from Cancellation token in Task constructor: why?
This is not an answer but too long to write in a comment
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
Task task = new Task(() =>
{
for (int i = 0; i < int.MaxValue; i++)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine("Task 2 - Int value {0}", i);
}
}, cancellationToken);
task.Start();
cancellationTokenSource.Cancel();
try
{
task.Wait();
}
catch (AggregateException ae)
{
if(ae.InnerExceptions.Single() is TaskCanceledException)
Console.WriteLine("Caught TaskCanceledException");
else
Console.WriteLine("Did not catch canceled");
}
Console.WriteLine("Task 2 cancelled? {0}", task.IsCanceled);
The code above prints what is expected, and I never got a print of 'Task 2 - Int value {0}' so I don't think it finished before canceling.
Note that canceling the task throws an exception in my sample
There are some patterns for handling exceptions using tasks that can be good to read about.