我有以下测试代码:
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);
}
在线程方法中,我没有抛出任何异常,但是我在外部代码的TaskCanceledException
块中得到了try-catch
,它启动了任务。为什么会发生这种情况,在这种情况下token.ThrowIfCancellationRequested();
的目的是什么。我相信只有在线程方法中调用token.ThrowIfCancellationRequested();
时才会抛出异常。
答案 0 :(得分:24)
我认为这是预期的行为,因为你正在参与竞争条件的变化。
来自How to: Cancel a task and its children:
调用线程不强制结束任务;它只表示要求取消。如果任务已在运行,则由用户委托决定通知请求并做出相应的响应。如果在任务运行之前请求取消,则永远不会执行用户委托,并且任务对象将转换为
Canceled
状态。
您可以通过简单地从代表返回来终止操作。在许多情况下,这已足够;但是,以这种方式“取消”的任务实例将转换为
RanToCompletion
状态,而不是Canceled
状态。
我在这里受过教育的猜测是,当你在呼唤你的两个任务.Start()
,有机会,一个(或两者)并没有真正开始你叫之前.Cancel()
您{{ 1}}。我打赌如果你在任务开始和取消之间至少等待三秒钟,它就不会抛出异常。此外,您可以检查两个任务的CancellationTokenSource
属性。如果我是对的,那么当抛出异常时,.Status
属性应至少读取其中一个TaskStatus.Canceled
。
请记住,启动新的.Status
并不能保证创建新的线程。它由TPL决定什么才能获得一个新线程以及什么只是排队等待执行。