【问题标题】:Task not Cancelling as expected任务未按预期取消
【发布时间】:2015-07-10 23:25:38
【问题描述】:

我们得到了以下场景:

class Program
{
    static void Main(string[] args)
    {
        // trigger the delayed function
        trigger();

        // cancel the running task.
        _token.Cancel();

        // keep window open ;-)
        Console.ReadLine();

    }

    private static CancellationTokenSource _token = null;
    private static async void trigger()
    {
        _token = new CancellationTokenSource();

        try
        {
            // run task
            await Task.Run(async () =>
            {
                // wait time
                await Task.Delay(2500);

                // we should be cancelled here !!
                Console.WriteLine(string.Format("IsCancellationRequested={0}", _token.Token.IsCancellationRequested));
                Console.WriteLine("SHOULD NOT HAPPEN");

            }, _token.Token);
        }
        catch (TaskCanceledException)
        {
        }
    }
}

IMO 预期的行为是任务的操作在处理 Task.Delay(2500) 之后大部分被取消。

但是控制台正在打印:

IsCancellationRequested=True
SHOULD NOT HAPPEN

这感觉就像一个错误。如果您将 CancellationToken 作为参数添加到 Task.Delay-Function,它将按预期工作。

那么如果任务内部的函数使用Task.Delay,你可能不知道如何处理取消?

【问题讨论】:

标签: c# task cancellation-token


【解决方案1】:

.Net 中的取消是合作的。将令牌作为参数传递给Task.Run 只会将令牌与返回的任务相关联。

要真正取消任务,您需要检查任务本身内部的令牌。如果您希望在延迟时间内取消任务,您需要将令牌传递给 Task.Delay 方法。否则只能在延迟之前或之后检查:

await Task.Run(async () =>
{
    _token.Token.ThrowIfCancelltionRequested();
    await Task.Delay(2500, _token.Token);
    _token.Token.ThrowIfCancelltionRequested();

    Console.WriteLine(string.Format("IsCancellationRequested={0}", _token.Token.IsCancellationRequested));
    Console.WriteLine("SHOULD NOT HAPPEN");

}, _token.Token);

【讨论】:

  • 此外,您不应该访问任务中的CancellationTokenSource,而是访问令牌本身。看MSDN example
  • 但是如果我在 Task.Delay 中传递令牌,它会产生一个异常,这会导致性能损失,我是对的吗?
  • 对我来说,为什么我可以通过 Task.Run 作为第二个参数传递令牌也不够清楚。这对我来说似乎毫无意义。
  • @ElMarchewko 您传递令牌有两个原因: 1. 如果令牌在任务开始之前被取消,那么它将在开始之前被抢先取消。 2. 当你调用ThrowIfCancelltionRequested 时,它只会抛出一个OperationCanceledException 并且返回的任务被标记为"faulted"。如果您将CancellationToken 传递给Task.Run,那么它会检查OperationCanceledException.CancellationToken 是否与传递的那个匹配,然后才将任务视为“取消”
【解决方案2】:

这是一篇不错的 MSDN 文章:https://msdn.microsoft.com/en-us/library/dd537607(v=vs.110).aspx

调用线程没有强制结束任务;它只发出信号 要求取消。如果任务已经在运行,它是 由用户委托通知请求并响应 适当地。如果在任务运行之前请求取消,则 用户委托永远不会被执行并且任务对象转换 进入已取消状态。

正如 i3arnon 建议的那样,您可以在执行实际工作之前检查状态并抛出异常。文章代码摘录:

...
  // Was cancellation already requested?  
      if (ct.IsCancellationRequested == true) {
         Console.WriteLine("Task {0} was cancelled before it got started.",
                           taskNum);
         ct.ThrowIfCancellationRequested();
      } 
...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-15
    • 1970-01-01
    • 1970-01-01
    • 2021-07-10
    • 1970-01-01
    • 1970-01-01
    • 2020-01-10
    • 1970-01-01
    相关资源
    最近更新 更多