52

好的,我的问题很简单。为什么这段代码不抛出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();
}
4

4 回答 4

45

托管线程中的取消

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

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

于 2014-03-25T14:40:24.400 回答
33

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

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

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

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

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

if (token.IsCancellationRequested) throw new OperationCanceledException(token);

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

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.
}
于 2015-11-10T19:26:17.840 回答
23

我认为是因为您没有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();
}

您的代码的第二个变体有效,因为您已经在初始化一个Canceled状态设置为 true 的令牌。确实,如此所述:

If canceled is true, both CanBeCanceled and IsCancellationRequested will be true

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

于 2014-03-25T14:40:43.340 回答
5

另一个使用 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;
        }
    }
于 2018-08-23T08:11:34.257 回答