在Task.Run中使用CancellationToken超时不起作用[重复]

问题描述 投票:0回答:4

好吧,我的问题很简单。为什么这段代码不会抛出

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

但是这个有效

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();
}
c# .net task-parallel-library cancellationtokensource cancellation-token
4个回答
57
投票

托管线程中的取消

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

您没有在

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


48
投票

取消正在运行的任务和计划运行的任务是有区别的。

调用Task.Run方法后,任务只是被调度,可能还没有执行。

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

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

根据MSDN,这只是以下的一种便捷方法:

if (令牌.IsCancellationRequested) 抛出新的OperationCanceledException(令牌);

注意这两种情况中使用的不同类型的异常:

catch (TaskCanceledException ex)
{
    // Task was canceled before running.
}
catch (OperationCanceledException ex)
{
    // Task was canceled while running.
}

另请注意,

TaskCanceledException
源自
OperationCanceledException
,因此对于
catch
类型,您可以只使用一个
OperationCanceledException
子句:

catch (OperationCanceledException ex)
{
    if (ex is TaskCanceledException)
        // Task was canceled before running.
    // Task was canceled while running.
}

29
投票

在代码的第一个变体中,您没有执行任何操作来管理取消令牌。

例如,您不会检查

token.IsCancellationRequested
是否返回
true
(然后引发异常)或从 CancellationToken 对象调用
ThrowIfCancellationRequested()

此外,您使用的

Task.Run
重载会检查任务即将开始时令牌是否已取消,并且您的代码表明令牌将在 500 毫秒后报告取消。 因此,您的代码只是忽略取消请求,这就是任务运行完成的原因。

你应该做这样的事情:

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

或者这个,不传递令牌,正如其他人已经指出的那样:

void Main()
{
    var ct = new CancellationTokenSource(500).Token;
    ct.ThrowIfCancellationRequested();
    var v = 
     Task.Run(() =>
    {
        Thread.Sleep(1000);
        return 10;
    }).Result;
    
    Console.WriteLine(v); //now a TaskCanceledException is thrown.
    Console.Read();
}

代码的第二个变体有效,因为您已经初始化了一个

Canceled
状态设置为 true 的令牌。事实上,正如这里所述:

如果取消是

true
CanBeCanceled
IsCancellationRequested
都将是
true

已经请求取消,然后将立即抛出异常

TaskCanceledException
,而不实际启动任务。


9
投票

另一种实现使用带有 token 的 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;
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.