I have the following test code:
void Button_Click(object sender, RoutedEventArgs e)
{
var source = new CancellationTokenSource();
var tsk1 = new Task(() => Thread1(source.Token), source.Token);
var tsk2 = new Task(() => Thread2(source.Token), source.Token);
tsk1.Start();
tsk2.Start();
source.Cancel();
try
{
Task.WaitAll(new[] {tsk1, tsk2});
}
catch (Exception ex)
{
// here exception is caught
}
}
void Thread1(CancellationToken token)
{
Thread.Sleep(2000);
// If the following line is enabled, the result is the same.
// token.ThrowIfCancellationRequested();
}
void Thread2(CancellationToken token)
{
Thread.Sleep(3000);
}
In the thread methods I don't throw any exceptions, but I get TaskCanceledException
in try-catch
block of the outer code which starts the tasks. Why this happens and what is the purpose of token.ThrowIfCancellationRequested();
in this case. I believe the exception should only be thrown if I call token.ThrowIfCancellationRequested();
in the thread method.
I believe this is expected behavior because you're running in to a variation of a race condition.
From How to: Cancel a task and its children:
and from Task Cancellation:
My educated guess here is that while you are calling
.Start()
on your two tasks, chances are that one (or both of them) didn't actually start before you called.Cancel()
on yourCancellationTokenSource
. I bet if you put in at least a three second wait between the start of the tasks and the cancellation, it won't throw the exception. Also, you can check the.Status
property of both tasks. If I'm right, the.Status
property should readTaskStatus.Canceled
on at least one of them when the exception is thrown.Remember, starting a new
Task
does not guarantee a new thread being created. It falls to the TPL to decide what gets a new thread and what is simply queued for execution.