【问题标题】:Why does TaskCanceledException occur?为什么会发生 TaskCanceledException?
【发布时间】:2013-02-17 09:04:28
【问题描述】:

我有以下测试代码:

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

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

【问题讨论】:

  • 这不会在 VS2017 .NET Framework 4.6.2 中抛出异常。

标签: c# task-parallel-library c#-5.0


【解决方案1】:

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

来自How to: Cancel a task and its children

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

来自Task Cancellation

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

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

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

【讨论】:

  • 是的,这是正确的。除了“由 TPL 决定什么获得新线程”,实际上问题中的 Start() 方法立即将任务排队到 ThreadPool 上,并且线程池(在堆栈中低于 TPL)决定何时实际执行工作。但是,如果在实际执行工作之前取消令牌,任务仍然会取消。
  • Task.WaitAll 有点糟糕,因为它在等待异步工作时阻塞了一个线程。如果您改为调用Task.WhenAll,您不仅会解除对线程的阻塞,而且它也不会抛出已取消的任务。但是,如果您随后 Wait() 或 await ,该方法将抛出的任务。
  • 所以这是设计使然,如果任务在开始之前被取消,它总是会抛出异常?
  • @net_prog 它不会总是抛出错误,它总是会取消。它出错的原因是我们在等待它。 await 被取消的东西会抛出错误,就像 await 错误的东西会一样。可以使用 .ContinueWith 或其他方法在回调中捕获取消和错误。
猜你喜欢
  • 2015-09-23
  • 1970-01-01
  • 1970-01-01
  • 2017-07-20
  • 2017-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-02
相关资源
最近更新 更多