为什么只有来自子任务的许多异常中的一个总是传播?

时间:2013-05-16 06:07:56

标签: c# logging task-parallel-library async-await visual-studio-debugging

我正在努力更好地掌握TPL中异常和错误处理的基本原理(并且在.NET 4.5 async / await tasks中有更多运气)

稍微修改了我之前的问题"How to better understand the code/statements from "Async - Handling multiple Exceptions" article?" C#控制台应用程序代码运行2 分离的内部嵌套附加(依赖)子项(更新:抱歉,启动了一个问题但由另一个问题结束!)任务:

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

似乎总是只有一个例外,其中一个子任务总是被传播/捕获。

为什么只传播/捕获一个例外?
如果没有或者更确切地说所有来自子任务的异常都被抓住了,我会更好地理解

在这种情况下,是否有可能会捕获这两个或没有异常?

2 个答案:

答案 0 :(得分:6)

您不应将父/子任务与async混合。它们的设计不是为了一起使用。

svick已作为his (correct) answer to your other question的一部分回答了这个问题。以下是你如何看待它:

  • 每个内部StartNew都会收到一个异常,该异常会包含在AggregateException中并放在返回的Task上。
  • 外部StartNew从其子任务中获取两个AggregateException,并将其包装到其返回的AggregateException上的另一个Task
  • 当您await Task时,会引发第一个内部异常。任何其他人都会被忽略。

您可以通过保存Task并在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运算符只传播其中一个,这就是为什么你只捕获一个。

答案 1 :(得分:-1)

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

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

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