如何在不抛出TaskCanceledExceptions的情况下等待任务?

问题描述:

我有一个方法可以创建一些任务,然后在返回之前用WaitAll等待它们。问题是,如果这些任务被取消,那么WaitAll会抛出包含大量TaskCanceledExceptionAggregateException如何在不抛出TaskCanceledExceptions的情况下等待任务?

这意味着,为WaitAll将在两种不同的情况下抛出异常:

  • 异常指示一个真正的错误。这意味着有一个我们不知道如何处理的情况;他们需要传播为未处理的异常,直到它们最终终止进程。
  • 指示用户单击取消按钮的异常。这意味着任务被取消和清理,程序应该继续正常运行。

后者适合正视到vexing exception的定义:它是在一个完全非特殊情况下抛出一个异常,所以我要抓住它,以恢复正常的控制流。幸运的是,它很容易捕捉,对吧?只需加上catch (AggregateException)和 - 哦,等等,这是在发生致命错误时引发的相同类型。

我确实需要等待任务在我返回之前完成运行(我需要知道他们不再使用他们的数据库连接,文件句柄或其他任何东西),所以我确实需要WaitAll或其他东西类似。如果任何任务出现故障,我确实希望这些异常作为未处理的异常传播。我只是不希望取消例外。

如何防止WaitAll为取消的任务抛出异常?

+0

使用需要的CancellationToken过载。 – 2011-12-30 16:29:34

+0

@HansPassant,那个超载的文档实际上并没有说明它使用了什么标记。它是否会忽略与该标记关联的TaskCanceledExceptions,或者如果标记被取消,它是否会简单地返回?我需要它不会返回,直到所有任务都停止运行。 – 2011-12-30 16:31:05

+1

您使用CancellationToken取消任务。并筛选您现在得到的特定OperationCancelException。 – 2011-12-30 16:33:13

AggregateException提供了一个Handle方法,可用于这些情况。例如,如果你想忽略TaskCanceledException你可以这样做:

var all = new AggregateException(
    new NullReferenceException(), 
    new TaskCanceledException(), 
    new TaskCanceledException(), 
    new InvalidOperationException(), 
    new TaskCanceledException()); 

try 
{ 
    throw all; 
} 
catch (AggregateException errors) 
{ 
    errors.Handle(e => e is TaskCanceledException); 
} 

如果所有的例外是TaskCanceledException类型时,Handle方法不会抛出任何异常;否则将抛出仅包含未处理的异常的新的AggregateException

基于João Angelo's suggestion,在这里不用一个任务类扩展

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading; 
using System.Threading.Tasks; 

namespace MySharedLibrary.Extensions 
{ 
    public static class TaskExtensions 
    { 

     // This code is based João Angelo's * suggestion https://*.com/a/8681687/378115 

     // Use this when a CancellationTokenSource is used 
     public static void SafeWait(this Task TargetTask, CancellationTokenSource TargetTaskCancellationTokenSource) 
     { 
      if (TargetTaskCancellationTokenSource.IsCancellationRequested == false) 
      { 
       TargetTaskCancellationTokenSource.Cancel(); 
      } 
      SafeWait(TargetTask); 
     } 

     // Use this when no CancellationTokenSource is used 
     public static void SafeWait(this Task TargetTask) 
     { 
      try 
      { 
       if (TargetTask.IsCanceled == false) 
       { 
        TargetTask.Wait(); 
       } 
      } 
      catch (AggregateException errors) 
      { 
       errors.Handle(e => e is TaskCanceledException); 
      } 
     } 

    } 
}