2

我正在努力更好地掌握 TPL 中异常和错误处理的基本原理(以及 .NET 4.5 异步/等待任务中的更多运气)

对我之前的问题“如何更好地理解“异步 - 处理多个异常”文章中的代码/语句?”稍作修改 运行 2 个分离的内部嵌套附加(依赖)子级的 C# 控制台应用程序代码(更新:抱歉,开始了一个问题但以另一个问题结束!)任务:

class Program
{  
   static void Main(string[] args)
   {  Tst();
      Console.ReadLine();
   }
   async static Task  Tst()
   {
       try
       {
           await Task.Factory.StartNew
             (() =>
                {
                   Task.Factory.StartNew
                       (   () => { 
                                    Console.WriteLine("From 1st child");
                                    throw new NullReferenceException(); 
                                  }
                            , TaskCreationOptions.AttachedToParent
                        );
               Task.Factory.StartNew
                       (  () =>
                               { 
                                   Console.WriteLine("From 2nd child");
                                   throw new ArgumentException(); 
                               }
      ,TaskCreationOptions.AttachedToParent
                       );
                }
             );
    }
    catch (AggregateException ex)
    {
        Console.WriteLine("** {0} **", ex.GetType().Name);
        foreach (var exc in ex.Flatten().InnerExceptions)
        {
             Console.WriteLine(exc.GetType().Name);
        }
    }
    catch (Exception ex)
    {
       Console.WriteLine("## {0} ##", ex.GetType().Name);
    }
 } 

产生在以下之间交替(非确定性)的输出:

From 1st child
From 2nd child
** AggregateException **
ArgumentException

From 1t child
From 2nd child
** AggregateException **
NullReferenceException

似乎总是传播/捕获子任务之一的一个且只有一个异常。

为什么只有一个异常传播/捕获?
我会更好地理解是否始终捕获子任务的所有异常或所有异常

在这种情况下,是否有可能同时捕获或不捕获异常?

4

2 回答 2

6

您不应将父/子任务与async. 它们并非设计为一起使用。

svick 已经回答了这个问题,作为他对您其他问题的(正确)回答的一部分。你可以这样想:

  • 每个内部StartNew都有一个异常,该异常被包装到 an 中AggregateException并放置在返回的Task.
  • 外部从其子任务中StartNew获取两个s,并在其返回AggregateException时将其包装到另一个中。AggregateExceptionTask
  • 当你awaitaTask时,会引发第一个内部异常。任何其他都被忽略。

Task您可以通过保存s 并在引发异常后检查它们来观察此行为await

async static Task Test()
{
    Task containingTask, nullRefTask, argTask;
    try
    {
        containingTask = Task.Factory.StartNew(() =>
        {
            nullRefTask = Task.Factory.StartNew(() =>
            {
                throw new NullReferenceException();
            }, TaskCreationOptions.AttachedToParent);
            argTask = Task.Factory.StartNew(() =>
            {
                throw new ArgumentException();
            }, TaskCreationOptions.AttachedToParent);
        });
        await containingTask;
    }
    catch (AggregateException ex)
    {
        Console.WriteLine("** {0} **", ex.GetType().Name);
    }
}

如果在 上放置断点WriteLine,您可以看到两个子任务的异常都被放置在父任务上。await运算符只传播其中一个,这就是为什么你只捕获一个。

于 2013-05-16T10:02:50.230 回答
-1

从我可以推断出发生这种情况的原因是 await 指示该任务等待任务完成。当抛出异常时,任务完成(因为异常导致它崩溃)并且异常向外传播到将被捕获的异步函数。这意味着您将始终使用此设置捕获一个异常。

要始终捕获两者,请删除 await 并改为使用 Task.Factory.StartNew(..).Wait(); Wait 函数将保留所有子进程的计数,并且在所有子进程完成之前不会返回。由于抛出了多个异常(每个子异常),它们被捆绑在一个新的 AggregateException 中,稍后会捕获该异常,并将其子级展平并打印内部异常。这应该给你输出:

From 1st child
From 2nd child
** AggregateException **
ArgumentException
NullReferenceException
于 2013-05-16T07:10:58.580 回答