Task.ContinueWith不适用于OnlyOnCanceled

时间:2015-10-15 19:31:46

标签: c# .net asynchronous task

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

  

您也可以添加延续,而不是捕获异常   仅在取消任务时执行的任务。在这个任务中,你   可以访问抛出的异常,您可以选择   如果合适的话,处理它。清单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);

这是我的完整主要方法代码:

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时,在task.Wait()调用时仍然会向Main方法抛出异常(AggregationException)。此外,如果我删除该调用,第二个任务永远不会运行(不会抛出异常)。有什么我做错了吗?是否可以在不使用try-catch的情况下处理异常?

3 个答案:

答案 0 :(得分:7)

要明确说明问题,您的第二个延续不会执行,但您认为它应该:

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(),续集中的task.Status也是RanToCompletion。另请注意,不会抛出AggregationException。这表明仅从令牌源调用.Cancel()未将任务状态设置为Canceled

仅调用.Cancel()且未调用.ThrowIfCancellationRequested()时,AggregationException实际上是成功取消任务的指标。引用MSDN article

  

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

这引出了我的结论:

清单1-44是known error

我的所有代码都省略了您的t.Exception...行,因为“任务的Exception属性会在成功取消后返回null ”。清单1-44中省略了 行。看起来他们正在混淆以下两种技术:

  1. 我的回答的第一个片段是取消任务的有效方法。调用OnlyOnCanceled延续并且不会抛出任何异常。
  2. 我的回答的第二个片段也是取消任务的有效方法,但未调用OnlyOnCanceled延续,并且在Task.Wait()处理时会抛出AggregationException
  3. 免责声明:两个片段都是取消任务的有效方法,但它们可能在我不了解的行为方面存在差异。

答案 1 :(得分:2)

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

您可以在MSDN上查看Task Cancellation了解详情。

以下是示例代码:

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

答案 2 :(得分:0)

试试这个:

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