的任务.ContinueWith不能与onlyoncancelled一起工作

本文关键字:onlyoncancelled 一起 工作 不能 任务 ContinueWith | 更新日期: 2023-09-27 18:18:04

在Microsoft关于使用CancellationToken的70-483考试的参考书中,用信号取消的第一种方法是抛出异常,然后引入第二种方法:

除了捕获异常,还可以添加一个延续只在取消任务时执行的任务。在这个任务中,你是否有权访问被抛出的异常,您可以选择如果合适的话处理它。清单1-44显示了这样一个延续任务看起来像

以下是列表1-44:

        Task task = Task.Run(() =>
        {
            while (!token.IsCancellationRequested)
            {
                Console.Write("*");
                Thread.Sleep(1000);
            }
        }, token).ContinueWith((t) =>
        {
            t.Exception.Handle((e) => true);
            Console.WriteLine("You have canceled the task");
        }, TaskContinuationOptions.OnlyOnCanceled);

这是我完整的Main方法代码:

static void Main(string[] args)
{
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    var token = cancellationTokenSource.Token;
    Task task = Task.Run(() =>
    {
        while (!token.IsCancellationRequested)
        {
            Console.Write("*");
            Thread.Sleep(1000);
        }
    }, token).ContinueWith((t) =>
    {
        t.Exception.Handle((e) => true);
        Console.WriteLine("You have canceled the task");
    }, TaskContinuationOptions.OnlyOnCanceled);
    Console.ReadLine();
    cancellationTokenSource.Cancel();
    task.Wait();
    Console.ReadLine();
}

然而,不像它所说的,当我按Enter时,异常(AggregationException)仍然在task.Wait()调用时抛出到Main方法。此外,如果我删除该调用,第二个Task将永远不会运行(不会抛出异常)。我做错了什么吗?不使用try-catch是否可以处理异常?

的任务.ContinueWith不能与onlyoncancelled一起工作

要明确说明问题,您的第二个continuation没有执行,但您认为应该执行:

static void Main(string[] args)
{
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    var token = cancellationTokenSource.Token;
    Task task = Task.Run(() =>
    {
        while (!token.IsCancellationRequested)
        {
            Console.Write("*");
            Thread.Sleep(1000);
        }
    }, token).ContinueWith((t) =>
    {                                                     //  THIS
        t.Exception.Handle((e) => true);                  //  ISN'T
        Console.WriteLine("You have canceled the task");  //  EXECUTING
    }, TaskContinuationOptions.OnlyOnCanceled);
    Console.ReadLine();
    cancellationTokenSource.Cancel();
    task.Wait();
    Console.ReadLine();
}

第二个延续没有执行,因为你必须使用token.ThrowIfCancellationRequested()来触发它:

        Task task = Task.Run(() =>
        {
            while (true)
            {
                token.ThrowIfCancellationRequested();  // <-- NOTICE
                Console.Write("*");
                Thread.Sleep(1000);
            }
        }, token).ContinueWith((t) =>
        {
            Console.WriteLine("From Continuation: " + t.Status);
            Console.WriteLine("You have canceled the task");
        }, TaskContinuationOptions.OnlyOnCanceled);
// OUTPUT:
// ***
// From Continuation: Canceled
// You have canceled the task
因为task.StatusCanceled,所以调用第二个延续。下一个代码片段不会触发第二个延续,因为task.Status没有设置为Canceled:
        Task task = Task.Run(() =>
        {
            while (!token.IsCancellationRequested)
            {
                Console.Write("*");
                Thread.Sleep(1000);
            }
        }, token).ContinueWith((t) =>
        {
            Console.WriteLine("From Continuation: " + t.Status);
            Console.WriteLine("You have canceled the task");
        }, TaskContinuationOptions.OnlyOnCanceled);
// OUTPUT:
// AggregationException

如前所述,没有调用第二个延续。让我们通过删除OnlyOnCanceled子句来强制执行它:

        Task task = Task.Run(() =>
        {
            while (!token.IsCancellationRequested)
            {
                Console.Write("*");
                Thread.Sleep(1000);
            }
        }, token).ContinueWith((t) =>
        {
            Console.WriteLine("From Continuation: " + t.Status);
            Console.WriteLine("You have NOT canceled the task");
        });   // <-- OnlyOnCanceled is gone!
// OUTPUT:
// ***
// From Continuation: RanToCompletion
// You have NOT canceled the task
// (no AggregationException thrown)

注意,尽管调用了.Cancel(),但是continuation中的task.StatusRanToCompletion。还要注意没有抛出AggregationException。这表明仅仅从令牌源调用.Cancel()并没有将任务状态设置为Canceled


当只调用.Cancel()而不调用.ThrowIfCancellationRequested()时,AggregationException实际上是任务取消成功的一个指标。引用MSDN文章:

如果您正在等待Task转换到Canceled状态,则会抛出System.Threading.Tasks.TaskCanceledException异常(包装在AggregateException异常中)。注意,此异常表示取消成功,而不是错误情况。因此,任务的Exception属性返回null

这让我得出了一个伟大的结论:

清单1-44是一个已知的错误。

您的t.Exception...行已从我的所有代码中省略,因为"任务的Exception属性在成功取消后返回null "。清单1-44中应该省略行。看起来他们把以下两种技术混为一谈了:

  1. 我的答案的第一个片段是取消任务的有效方法。OnlyOnCanceled的延续被调用并且不抛出异常。
  2. 我的答案的第二个片段也是一个有效的方式来取消一个任务,但OnlyOnCanceled延续不被调用,并抛出一个AggregationException为您处理在Task.Wait()

免责声明:这两个片段都是取消任务的有效方法,但它们可能在行为上有我不知道的差异。

使用cancellationTokenSource.Cancel()取消的任务实例将具有TaskStatus.RanToCompletion状态,而不是TaskStatus.Canceled状态。所以我认为你应该把TaskContinuationOptions.OnlyOnCanceled改成TaskContinuationOptions.OnlyOnRanToCompletion

您可以在MSDN上查看任务取消以获取更多详细信息。

下面是示例代码:

 CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
Task task = Task.Run(() =>
{
    while (!token.IsCancellationRequested)
    {
        Console.Write("*");
        Thread.Sleep(1000);
    }
}, token).ContinueWith((t) =>
{
    t.Exception.Handle((e) => true);
    Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnRanToCompletion);
Console.ReadLine();
cancellationTokenSource.Cancel();
try
    {
        task.Wait();
    }
catch (AggregateException e)
    {
        foreach (var v in e.InnerExceptions)
            Console.WriteLine(e.Message + " " + v.Message);
    }
Console.ReadLine();

试试这个:

        Task task = Task.Run(() =>
        {
            while (true) {
                token.ThrowIfCancellationRequested();
                Console.Write("*");
                Thread.Sleep(1000);
            }
        }, token).ContinueWith((t) =>
        {
            //t.Exception.Handle((e) => true); //there is no exception
            Console.WriteLine("You have canceled the task");
        }, TaskContinuationOptions.OnlyOnCanceled);