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.