73

我有以下测试代码:

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);
}

在线程方法中,我没有抛出任何异常,但我进入TaskCanceledExceptiontry-catch启动任务的外部代码块。为什么会发生这种情况以及token.ThrowIfCancellationRequested();在这种情况下的目的是什么。token.ThrowIfCancellationRequested();我相信只有在调用线程方法时才应该抛出异常。

4

1 回答 1

40

我相信这是预期的行为,因为您遇到了竞争条件的变化。

如何:取消任务及其子项

调用线程不强制结束任务;它仅表示请求取消。如果任务已经在运行,则由用户委托来通知请求并做出适当的响应。如果在任务运行之前请求取消,则永远不会执行用户委托并且任务对象转换到Canceled状态。

并从任务取消

您可以通过 [...] 简单地从委托返回来终止操作。在许多情况下,这已经足够了;但是,以这种方式“取消”的任务实例会转换到RanToCompletion状态,而不是Canceled状态。

我有根据的猜测是,当您调用.Start()两项任务时,很可能其中一项(或两项)实际上并未在您.Cancel()调用CancellationTokenSource. 我敢打赌,如果您在任务开始和取消之间至少等待三秒钟,它就不会抛出异常。此外,您可以检查这.Status两个任务的属性。如果我是对的,那么当抛出异常时,该属性应该至少.Status读取其中一个。TaskStatus.Canceled

请记住,开始一个Task新线程并不能保证创建一个新线程。由 TPL 决定什么获得一个新线程以及什么简单地排队等待执行。

于 2013-03-03T05:21:08.137 回答