在Task.Run中使用CancellationToken超时不起作用

Ali*_*tad 32 .net c# task-parallel-library cancellationtokensource cancellation-token

好的,我的问题非常简单.为什么这段代码不会抛出TaskCancelledException

static void Main()
{
    var v = Task.Run(() =>
    {
        Thread.Sleep(1000);
        return 10;
    }, new CancellationTokenSource(500).Token).Result;

    Console.WriteLine(v); // this outputs 10 - instead of throwing error.
    Console.Read();
}
Run Code Online (Sandbox Code Playgroud)

但这一个有效

static void Main()
{
    var v = Task.Run(() =>
    {
        Thread.Sleep(1000);
        return 10;
    }, new CancellationToken(true).Token).Result;

    Console.WriteLine(v); // this one throws
    Console.Read();
}
Run Code Online (Sandbox Code Playgroud)

Dam*_*ver 32

托管线程中的取消:

取消是合作的,不会强迫听众.侦听器确定如何优雅地终止以响应取消请求.

您没有在Task.Run方法中编写任何代码来访问您CancellationToken并实现取消 - 因此您实际上忽略了取消请求并运行完成.

  • @Aliostad - "任务"可能会等待在代码实际运行之前的任何时间内安排.对于已提供取消令牌的任务,一个明显的优化是在您在线程上实际安排任务之前检查该令牌.在第二种情况下,执行该检查时,已经请求取消,因此任务实际上从未启动过. (9认同)

Geo*_*voy 20

取消正在运行的任务和计划运行的任务有所不同.

在调用Task.Run方法之后,该任务仅被调度,并且可能尚未执行.

当您使用具有取消支持的Task.Run(...,CancellationToken)重载系列时,将在任务即将运行时检查取消令牌.如果取消令牌此时将IsCancellationRequested设置为true,则抛出TaskCanceledException类型的异常.

如果任务已在运行,则任务负责调用ThrowIfCancellationRequested方法,或者只是抛出OperationCanceledException.

根据MSDN,它只是一种方便的方法:

if(token.IsCancellationRequested)抛出新的OperationCanceledException(token);

不是在这两种情况下使用的不同类型的异常:

catch (TaskCanceledException ex)
{
    // Task was canceled before running.
}
catch (OperationCanceledException ex)
{
    // Task was canceled while running.
}
Run Code Online (Sandbox Code Playgroud)

还要注意TaskCanceledException派生自OperationCanceledException,所以你可以只catch为该OperationCanceledException类型有一个子句:

catch (OperationCanceledException ex)
{
    if (ex is TaskCanceledException)
        // Task was canceled before running.
    // Task was canceled while running.
}
Run Code Online (Sandbox Code Playgroud)


Alb*_*ano 19

我想因为你没有ThrowIfCancellationRequested()从CancellationToken对象调用该方法.以这种方式,你忽略了取消任务的请求.

你应该做这样的事情:

void Main()
{
    var ct = new CancellationTokenSource(500).Token;
     var v = 
     Task.Run(() =>
    {
        Thread.Sleep(1000);
        ct.ThrowIfCancellationRequested();
        return 10;
    }, ct).Result;

    Console.WriteLine(v); //now a TaskCanceledException is thrown.
    Console.Read();
}
Run Code Online (Sandbox Code Playgroud)

代码的第二个变体可以工作,因为您已经初始化Canceled状态设置为true 的标记.事实上,正如说在这里:

If canceled is true, both CanBeCanceled and IsCancellationRequested will be true
Run Code Online (Sandbox Code Playgroud)

已经请求取消,然后TaskCanceledException立即抛出异常,而不实际启动任务.

  • 还有一个问题:Task.Run(task, cancel) 和 Task.Run(task) 有什么区别?ct 变量传递给 Task.Run,​​但似乎没有在任何地方使用。 (3认同)
  • 但是将ct传递给Task.Run有什么意义呢? (2认同)
  • 由于`ct.ThrowIfCancellationRequested();`实际上引用了闭包中的`ct`变量,如果我省略传递给`Task.Run()`的`ct`,我可以得到相同的结果.对? (2认同)
  • 通过 ct 会在任务开始前自动取消。如果没有,你应该检查代码中的ct状态,任务正在运行。 (2认同)

Z.R*_*.T. 9

另一种实现是使用带有令牌的 Task.Delay 来代替 Thread.Sleep。

 static void Main(string[] args)
    {
        var task = GetValueWithTimeout(1000);
        Console.WriteLine(task.Result);
        Console.ReadLine();
    }

    static async Task<int> GetValueWithTimeout(int milliseconds)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;
        cts.CancelAfter(milliseconds);
        token.ThrowIfCancellationRequested();

        var workerTask = Task.Run(async () =>
        {
            await Task.Delay(3500, token);
            return 10;
        }, token);

        try
        {
            return await workerTask;
        }
        catch (OperationCanceledException )
        {
            return 0;
        }
    }
Run Code Online (Sandbox Code Playgroud)