As I read the documentation CancellationTokenSource.Cancel is not supposed to throw an exception.
CancellationTokenSource.Cancel
Below the call to cts.Cancel(); is causing (not throwing) a OperationCanceledException.
I am very confident of that as if I comment that line out then that last OperationCanceledException is not thrown.
With cts.Cancel line active the line that is throwing the exception is t2.Wait(token);
And if I have a delay in cts.Cancel(); then t2.Wait(token); does not throw the exception as soon as the line is called.
t2.Wait(token); only throws that exception when cts.Cancel() is run.
Is that proper behavior?
If it is consistent then I can live with it but I don't want cts.Cancel to cause an exception.
I am clearly confused - I just want to understand the behavior so I can feel comfortable taking this to a production environment.
Right now I am doing this with a BackGroundWorker and I thought I could make it easier follow and maintain using wait and cancel.
if (token.IsCancellationRequested || ctr == 100000000)
still throws on ctr == 100000000
I still see that inner OperationCanceledException caught and outer (lower) is not thrown at all.
Is there something wrong with this code?
Or is that how it is supposed to work?
Without the try catch in Task t2 = Task.Run I was getting an uncaught exception was thrown.
I thought that would have been caught by the try catch on t2.Wait but one question at a time.
This is console app on .NET 4.5.
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task.Run(() =>
{
Thread.Sleep(1000);
cts.Cancel(); // this is thowing an exception that is caught on the last catch (OperationCanceledException Ex)
// according to the documentation this is not supposed
// if I comment out the cts.Cancel(); then the exeption is not thrown
if (token.IsCancellationRequested)
Console.WriteLine("Cancellation requested in Task {0}.",
Task.CurrentId);
else
Console.WriteLine("Cancellation Not requested in Task {0}.",
Task.CurrentId);
}, token);
//tried this but did not help
//Task.Run(() =>
//{
// //Thread.Sleep(1000);
// if (token.IsCancellationRequested)
// Console.WriteLine("Cancellation requested in Task {0}.",
// Task.CurrentId);
//}, token);
//Thread.Sleep(1000);
////cts.Cancel();
Task t2 = Task.Run(() =>
{
try
{
Console.WriteLine("Task t2 started Int32.MaxValue = " + Int32.MaxValue.ToString());
Thread.Sleep(4000);
for (int ctr = 0; ctr < Int32.MaxValue; ctr++)
{
if (ctr < 100 || ctr % 100000000 == 0)
{
Console.WriteLine(ctr.ToString());
}
if (token.IsCancellationRequested || ctr == 100000000) // || ctr == 100000000
{
Console.WriteLine("ThrowIfCancellationRequested in t2 Task {0}.",
Task.CurrentId);
throw new OperationCanceledException(token);
//token.ThrowIfCancellationRequested();
}
}
Console.WriteLine("Task {0} finished.",
Task.CurrentId);
}
catch (OperationCanceledException Ex)
{
//Console.WriteLine(Ex.ToString());
Console.WriteLine("OperationCanceledException in Task t2 {0}: The operation was cancelled.",
Task.CurrentId);
}
catch (Exception Ex)
{
Console.WriteLine("Task t2 = Task.Run Exception Ex" + Ex.Message);
}
});
try
{
Console.WriteLine("t2.Wait a");
t2.Wait(token);
}
catch (AggregateException e)
{
Console.WriteLine("AggregateException");
foreach (var v in e.InnerExceptions)
Console.WriteLine(e.Message + " " + v.Message);
}
catch (OperationCanceledException Ex)
{
//Console.WriteLine(Ex.ToString());
Console.WriteLine("OperationCanceledException in Task {0}: The operation was cancelled.",
t2.Id);
}
catch (Exception Ex)
{
Console.WriteLine(Ex.ToString());
}
Console.WriteLine("end");
Console.ReadLine();
}
Yes, this is the expected behavior:
The overload of Task.Wait
that takes a cancellation token waits until:
In the latter case, when Task.Wait
observes that the passed in token has been cancelled, it throws an 'OperationCancelledException'. Here's the call stack when the exception is triggered in your case
mscorlib.dll!System.Threading.CancellationToken.ThrowOperationCanceledException() Line 505 + 0x4d bytes C#
mscorlib.dll!System.Threading.ManualResetEventSlim.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Line 642 C#
mscorlib.dll!System.Threading.Tasks.Task.SpinThenBlockingWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Line 3284 + 0xf bytes C#
mscorlib.dll!System.Threading.Tasks.Task.InternalWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Line 3223 + 0x10 bytes C#
mscorlib.dll!System.Threading.Tasks.Task.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Line 3129 + 0xc bytes C#
mscorlib.dll!System.Threading.Tasks.Task.Wait(System.Threading.CancellationToken cancellationToken) Line 3064 + 0xe bytes C#
> ConsoleApplication1.exe!ConsoleApplication1.Program.Main(string[] args) Line 82 + 0x15 bytes C#
As to your second part of the question: If you - removed the try/catch within t2 and - you never cancelled the token,
then you would end up landing in your outer AggregateException
handler (since the call to t2.Wait
would throw an AggregateException
with one inner exception which would be the OperationCanceledException
that you threw.