Task.IsCancelled doesn't work - c#

I've got the following sample code:
static class Program
{
static void Main()
{
var cts = new CancellationTokenSource();
var task = Task.Factory.StartNew(
() =>
{
try
{
Console.WriteLine("Task: Running");
Thread.Sleep(5000);
Console.WriteLine("Task: ThrowIfCancellationRequested");
cts.Token.ThrowIfCancellationRequested();
Thread.Sleep(2000);
Console.WriteLine("Task: Completed");
}
catch (Exception exception)
{
Console.WriteLine("Task: " + exception.GetType().Name);
throw;
}
}).ContinueWith(t => Console.WriteLine("ContinueWith: cts.IsCancellationRequested = {0}, task.IsCanceled = {1}, task.Exception = {2}", cts.IsCancellationRequested, t.IsCanceled, t.Exception == null ? "null" : t.Exception.GetType().Name));
Thread.Sleep(1000);
Console.WriteLine("Main: Cancel");
cts.Cancel();
try
{
Console.WriteLine("Main: Wait");
task.Wait();
}
catch (Exception exception)
{
Console.WriteLine("Main: Catch " + exception.GetType().Name);
}
Console.WriteLine("Main: task.IsCanceled = {0}", task.IsCanceled);
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
}
The output is:
Task: Running
Main: Cancel
Main: Wait
Task: ThrowIfCancellationRequested
Task: OperationCanceledException
ContinueWith: cts.IsCancellationRequested = True, task.IsCanceled = False, task.Exception = AggregateException
Main: task.IsCanceled = False
Press any key to exit...
If I remove ContinueWith, then the output is:
Task: Running
Main: Cancel
Main: Wait
Task: ThrowIfCancellationRequested
Task: OperationCanceledException
Main: Catch AggregateException
Main: task.IsCanceled = False
Press any key to exit...
I don't understand, why task.IsCanceled returns false in both cases?
And why exception is rethrown only without ContinueWith?
What I'm trying to achieve is an uniform and simple way for waiting for task completion and a property that will indicate if task was cancelled or not.

I think you are not cancelling the task itself, but just throwing an exception from a task. Try using StartNew(Action action,CancellationToken cancellationToken) instead of StartNew(Action action). You can also add cancellation token as a parameter to ContinueWith.

Related

cancelling tasks throws exception but task continues

I am trying to cancel a long-running task which is simulated bay an infinite loop printing to the console. Cancellation token is invoked from the main thread after 2 seconds. Even though the output on the console says "token IsCancellationRequested true", the loop continues. What is the problem here?
private static void CancelingTasks2()
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
t = Task.Factory.StartNew(() =>
{
if (token.IsCancellationRequested)
throw new OperationCanceledException("cancelled on the token", token);
print("printing for ever ...");
}, token);
Thread.Sleep(2000);
cts.Cancel();
Console.WriteLine("canceled");
Console.WriteLine("task status " + t.Status);
Console.WriteLine("token IsCancellationRequested " + token.IsCancellationRequested);
}
private static void print(string txt)
{
while (true)
{
Console.WriteLine(txt); Thread.Sleep(500);
}
}
Use Task.Run instead of Task.Factory.StartNew and try to avoid mixing Task and Thread.Sleep. Use Task.Delay. If using Task then the code needs to be async all the way.
Your loop continues because there is nothing to break out of the loop.
A rewrite of the above example with proper syntax would look like this
public class Program {
public static async Task Main(string[] args) {
Console.WriteLine("Hello");
await CancelingTasks2();
Console.WriteLine("Exit");
}
private static async Task CancelingTasks2() {
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
var t = print("printing for ever ...", token);
await Task.Delay(2000);
cts.Cancel();
Console.WriteLine("canceled");
Console.WriteLine("task status " + t.Status);
Console.WriteLine("token IsCancellationRequested " + token.IsCancellationRequested);
}
private static async Task print(string txt, CancellationToken token) {
while (true) {
if (token.IsCancellationRequested)
throw new OperationCanceledException("cancelled on the token", token);
Console.WriteLine(txt);
await Task.Delay(500);
}
}
}
And produce the following output when run
Hello
printing for ever ...
printing for ever ...
printing for ever ...
printing for ever ...
printing for ever ...
canceled
task status WaitingForActivation
token IsCancellationRequested True
Exit
Fiddle
Looks like you do an infinite loop in print, not checking the token cancellation more than once.
You should put you if inside the while loop:
while (true)
{
if (token.IsCancellationRequested)
{
throw new OperationCanceledException(
"cancelled on the token", token);
}
Console.WriteLine(txt);
Thread.Sleep(500);
}

Task Status Value is Faulted instead Cancelled

I need to call a method in action of my Task object. My task performs some kind of reading and I cancel the operation if it takes more than 2 seconds to complete.
I have this code as simulation:
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(2000));
var task = Task.Run(() =>
{
try
{
int i = 0;
while (true)
{
Thread.Sleep(500);
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine("i = {0}", i);
i++;
if (i > 3) throw new InvalidOperationException();
}
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
throw;
}
});
task.ContinueWith(t => Console.WriteLine(t.Status), TaskContinuationOptions.NotOnRanToCompletion);
My console outout is as follows:
This is what I expect and works for me. If I copy the code inside the task and create a method I no longer get the task status as Cancelled. I get status as Faulted. I must know if the operation was cancelled or an exception happened while reading process. I cannot figure out why I do not get the task status as Cancelled here.
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(2000));
var task = Task.Run(() =>
{
try
{
Tester(cts);
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
throw;
}
});
private static void Tester(CancellationTokenSource cts)
{
int i = 0;
while (true)
{
Thread.Sleep(500);
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine("i = {0}", i);
i++;
if (i > 3) throw new InvalidOperationException();
}
}
You get the expected Canceled result, if you pass the token to Task.Run:
var task = Task.Run(() =>
{
try
{
Tester(cts);
}
catch (Exception e)
{
Console.WriteLine("Exception {0}", e.Message);
throw;
}
}, cts.Token);
The task needs to know which token will throw the OperationCanceledException - only an exception from the correct source cancels the task, every other exception will just fault it.
From MSDN:
When a task instance observes an OperationCanceledException thrown by user code, it compares the exception's token to its associated token (the one that was passed to the API that created the Task). If they are the same and the token's IsCancellationRequested property returns true, the task interprets this as acknowledging cancellation and transitions to the Canceled state.
I don't fully understand why you get the Canceled result in the first place, it looks like it has to do with when the CancellationTokenSource is captured.

How to cancel a Task using CancellationToken?

So I've this code:
//CancelationToken
CancellationTokenSource src = new CancellationTokenSource();
CancellationToken ct = src.Token;
ct.Register(() => Console.WriteLine("Abbruch des Tasks"));
//Task
Task t = new Task(() =>
{
System.Threading.Thread.Sleep(1000);
if (ct.IsCancellationRequested)
{
try
{
//Throw
ct.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
Console.WriteLine(
"ThrowIfCancellationRequested() liefert eben eine Exception");
}
}
}, ct);
//Run Task and Cancel
t.Start();
src.CancelAfter(350);
t.Wait();
// Get Information
Console.WriteLine("Canceled: {0} . Finished: {1} . Error: {2}",
t.IsCanceled, t.IsCompleted, t.IsFaulted);
So in this case I canceled my Task but my output in the end is:
"Canceled: False . Finished: True . Error: False"
In my opinion it should be "Canceled:True . Finished:False".
Why do I get this result? Because I try to catch the exception?
I've tried it without the try - catch block, but then my program stops because of the OperationCanceledException. Can somebody help me?
You're swallowing the exception, thus the task is flagged as finished as you actually handle the exception and it doesn't propagate outwards.
Instead, don't catch the exception inside the delegate, catch it outside:
void Main()
{
CancellationTokenSource src = new CancellationTokenSource();
CancellationToken ct = src.Token;
ct.Register(() => Console.WriteLine("Abbruch des Tasks"));
Task t = Task.Run(() =>
{
System.Threading.Thread.Sleep(1000);
ct.ThrowIfCancellationRequested();
}, ct);
src.Cancel();
try
{
t.Wait();
}
catch (AggregateException e)
{
// Don't actually use an empty catch clause, this is
// for the sake of demonstration.
}
Console.WriteLine("Canceled: {0} . Finished: {1} . Error: {2}",
t.IsCanceled, t.IsCompleted, t.IsFaulted);
}

Canceled task also appears as completed

I'm playing around with async-await and cancellation to get some more understanding on the matter. For this I have made the following console application:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncTest
{
class Program
{
private static CancellationTokenSource _cancellationTokenSource;
private static CancellationToken _cancellationToken;
static void Main(string[] args)
{
Console.CancelKeyPress += myHandler;
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
var task = DoWorkAsync(_cancellationToken).ContinueWith(ContinueMethod);
task.Wait();
Console.ReadLine();
}
protected static void myHandler(object sender, ConsoleCancelEventArgs args)
{
if (_cancellationToken.CanBeCanceled)
{
_cancellationTokenSource.Cancel();
}
args.Cancel = true;
}
static void ContinueMethod(Task task)
{
if (task.IsCanceled)
{
Console.WriteLine("The task was canceled");
}
if (task.IsCompleted)
{
Console.WriteLine("The task completed successfully");
}
if (task.IsFaulted)
{
if (task.Exception != null)
{
var exceptions = task.Exception.Flatten().InnerExceptions;
foreach (var exception in exceptions)
{
Console.WriteLine(exception.Message);
}
}
Console.WriteLine("The task failed");
}
}
static async Task DoWorkAsync(CancellationToken cancellationToken)
{
await Task.Run(() => DoWork(cancellationToken), cancellationToken);
}
static void DoWork(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine("DoWork() is started");
// Uncomment the following line of code to put the task in a 'faulted' state
//throw new Exception();
for (var count = 0; count < 10; count++)
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Get a cancelation request");
cancellationToken.ThrowIfCancellationRequested();
}
else
{
Thread.Sleep(500);
Console.WriteLine("Count : " + count);
}
}
Console.WriteLine("DoWork() is finished");
}
}
}
When I let the application complete, I correctly receive the "The task completed successfully" message.
Now when I press CTRL+C, which triggers a cancel on the started task (see interception through myHandler), I correctly get the "The task was canceled" message. But I also get the "The task completed successfully" message. I was not expecting the task to also show up as complete, since I canceled it.
In case I uncomment the throw new Exception(); line in the DoWork() method, I correctly receive the "The task failed" message, but also the "The task completed successfully" message.
Am I wrong in my assumption and is this as designed? Or am I missing something else entirely?
I could off course work around this by adding an additional check as follows:
if (task.IsCompleted && !task.IsCanceled)
{
Console.WriteLine("The task completed successfully");
}
But I'm not sure if this is the correct way or if something else in my program is causing this completed state.
Thanks in advance for your input and/or clarification on this matter.
The documentation of Task.IsCompleted says
IsCompleted will return true when the task is in one of the three final states: RanToCompletion, Faulted, or Canceled.
So IsCompleted tells you at least that the Task is not running any more. It does not indicate if the Task completed successfully, failed or was cancelled.
Use Task.IsCompletedSuccessFully

Throw an aggregate exception from a task won't break all other tasks

I have two tasks:
try
{
List<Task> tasks = new List<Task>();
Task task1 = Task.Factory.StartNew(() => func(param));
tasks.Add(task1);
if (someCondition)
{
Task task2 = Task.Factory.StartNew(() => func2(param2));
tasks.Add(task2);
}
tasksArr = tasks.ToArray();
Task.WaitAll(tasksArr);
}
catch (AggregateException e)
{
//handle
}
catch (Exception)
{
//handle
}
If an exception occurs in one of the tasks, I'm throwing a new aggregation exception, but 'm reaching the catch statement only after the second task finishes. I want to catch it immediately, in order to stop unnecessary work in the second task.
I tried to use a cancellation token:
CancellationTokenSource cts = new CancellationTokenSource();
Task.WaitAll(tasksArr,cts.Token);
But this wasn't a good solution since it requires that the inner exception will be handled + the caught exception will be an OperationCanceledException
Any ideas?
To cancel waiting it is necessary to pass an instance of CancellationTokenSource class to Task.WaitAll Method (Task[], CancellationToken). Also, use a continuation on each task that runs when a task fails (faults, i.e. TaskStatus.Faulted).
var cancellationTokenSource = new CancellationTokenSource();
var tasks = new List<Task>();
var firstTask = new Task(FirstMethod);
firstTask.ContinueWith(t =>
{
Console.WriteLine("First task exception: {0}", t.Exception);
cancellationTokenSource.Cancel();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
tasks.Add(firstTask);
firstTask.Start();
var secondTask = new Task(SecondMethod);
secondTask.ContinueWith(t =>
{
Console.WriteLine("Second task exception: {0}", t.Exception);
cancellationTokenSource.Cancel();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
tasks.Add(secondTask);
secondTask.Start();
try
{
var cancellationToken = cancellationTokenSource.Token;
var tasksArray = tasks.ToArray();
Task.WaitAll(tasksArray, cancellationToken);
}
catch (Exception e)
{
// ...
}

Categories

Resources