Reputation: 14399
I never call cts.Cancel()
, so why does it throw TaskCanceledException
exception?
var cts = new CancellationTokenSource();
try
{
await Task.Factory.StartNew(() =>
{
throw new Exception("");
}, cts.Token)
.ContinueWith(t =>
{
}, TaskContinuationOptions.OnlyOnCanceled);
}
catch (TaskCanceledException ex)
{
throw;
}
Upvotes: 1
Views: 1042
Reputation: 456427
Others have noted why this is happening: as documented, the continuation task is canceled if the conditions specified in ContinueWith
are not met.
If the criteria specified through the continuationOptions parameter are not met, the continuation task will be canceled instead of scheduled.
The best way to avoid this problem is not to use ContinueWith
at all. You should also not use StartNew
or TaskCanceledException
. Use await
instead of ContinueWith
, Task.Run
instead of Task.Factory.StartNew
, and OperationCanceledException
instead of TaskCanceledException
.
Your resulting code will be more correct and also shorter, cleaner, and easier to maintain.
Upvotes: 3
Reputation: 111
It throws TaskCancelledException because of the Chained Task (task inside ContinueWith Method). Once you throw an exception inside your code, the task finishes with faulted result and starts the second task. When you use await (await Task.Factory ...) you await the SECOND task, which is cancelled because the previous task was failed (not cancelled), therefore, you get the task cancelled exception.
Await will throw any existing exception in the chain in to the calling thread. So you are getting the last exception in the chain (from the second task).
You can see in this example what code is executing in the test cases based on your code. Please note I removed the async keyword before ...StartNew(() => $ ...., and that CTS on Task.Run / Task.StartNew only prevents task for starting but wont stop the task unless you directly check for cancellation inside Task body.
Check the reference: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
<pre>
var cases = new[] {
"Task normally run",
"Task with cancellation timedout before run",
"Task with cts timing out during run",
"Task with cancellation inside method (calling Cancel())"
};
var cancellationTimeout = new[] { 3000, 1, 500, 3000 };
var delay = new[] { 0, 500, 0, 0 };
var cancel = new[] { false, false, false, true };
for (int i = 0; i < cancellationTimeout.Length; i++)
await test(i);
async Task test(int i)
{
Console.WriteLine($"Test case {i + 1}. {cases[i]}");
Console.WriteLine($" Cancellation Timeout: {cancellationTimeout[i]} ms, \n Delay Start: {delay[i]} ms, \n Cancel task 1: {cancel[i]}, \n Task 1 duration: 750ms");
Console.WriteLine($"=== Console log ===");
var cts = new CancellationTokenSource(cancellationTimeout[i]);
try
{
await Task.Delay(delay[i]);
var t1 = Task.Factory.StartNew(() =>
{
Console.WriteLine(" Task 1 started");
if (cancel[i])
{
Console.WriteLine(" This is executed if the cancellation token is requested");
cts.Cancel();
Console.WriteLine(" This is executed when you check for cancellation");
cts.Token.ThrowIfCancellationRequested();
Console.WriteLine(" This is executed after throw");
}
Task.Delay(500).Wait();
Console.WriteLine(" Task 1 finished");
}, cts.Token); //This canellation token only works if the current task has not started and cancellation request is .
var t2 = t1.ContinueWith(t =>
{
Console.WriteLine(" This is executed if the task 1 is cancelled and handled");
}, TaskContinuationOptions.OnlyOnCanceled);
Exception? Ex1 = null;
try
{
await t1;
}
catch (Exception ex) { Ex1 = ex; }
Exception? Ex2 = null;
try
{
await t2;
}
catch (Exception ex) { Ex2 = ex; }
Console.WriteLine($"=== Tasks ===");
Console.WriteLine($" Task 1 status:{t1.Status,20} Faulted?:{t1.IsFaulted,10} Ex:{Ex1?.Message}");
Console.WriteLine($" Task 2 status:{t2.Status,20} Faulted?:{t2.IsFaulted,10} Ex:{Ex2?.Message}");
Console.WriteLine("");
}
catch (TaskCanceledException ex)
{
throw;
}
}
</pre>
You can check the following results.
Test case 1. Task normally run
Cancellation Timeout: 3000 ms,
Delay Start: 0 ms,
Cancel task 1: False,
Task 1 duration: 750ms
=== Console log ===
Task 1 started
Task 1 finished
=== Tasks ===
Task 1 status: RanToCompletion Faulted?: False Ex:
Task 2 status: Canceled Faulted?: False Ex:A task was canceled.
Test case 2. Task with cancellation timedout before run
Cancellation Timeout: 1 ms,
Delay Start: 500 ms,
Cancel task 1: False,
Task 1 duration: 750ms
=== Console log ===
This is executed if the task 1 is cancelled and handled
=== Tasks ===
Task 1 status: Canceled Faulted?: False Ex:A task was canceled.
Task 2 status: RanToCompletion Faulted?: False Ex:
Test case 3. Task with cancellation timeout during run
Cancellation Timeout: 500 ms,
Delay Start: 0 ms,
Cancel task 1: False,
Task 1 duration: 750ms
=== Console log ===
Task 1 started
Task 1 finished
=== Tasks ===
Task 1 status: RanToCompletion Faulted?: False Ex:
Task 2 status: Canceled Faulted?: False Ex:A task was canceled.
Test case 4. Task with cancellation inside method
Cancellation Timeout: 3000 ms,
Delay Start: 0 ms,
Cancel task 1: True,
Task 1 duration: 750ms
=== Console log ===
Task 1 started
This is executed if the cancellation token is requested
This is executed when you check for cancellation
This is executed if the task 1 is cancelled and handled
=== Tasks ===
Task 1 status: Canceled Faulted?: False Ex:The operation was canceled.
Task 2 status: RanToCompletion Faulted?: False Ex:
Good Luck
Upvotes: 1
Reputation: 1093
TaskCanceledException
is thrown because TaskContinuationOptions.OnlyOnCanceled
option is set for the continuation task, which causes the continuation task to throw a TaskCanceledException
if the antecedent task (the initial task) throws any exception, regardless of whether the antecedent task was actually canceled.
Upvotes: 0