Why is only one from many exceptions from child tasks always propagated?

爷,独闯天下 提交于 2019-12-01 11:09:18
Stephen Cleary

You should not mix parent/child tasks with async. They were not designed to go together.

svick already answered this question as part of his (correct) answer to your other question. Here's how you can think of it:

  • Each inner StartNew gets one exception, which is wrapped into an AggregateException and placed on the returned Task.
  • The outer StartNew gets both AggregateExceptions from its child tasks, which it wraps into another AggregateException on its returned Task.
  • When you await a Task, the first inner exception is raised. Any others are ignored.

You can observe this behavior by saving the Tasks and inspecting them after the exception is raised by 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);
    }
}

If you put a breakpoint on WriteLine, you can see that the exceptions from both child tasks are being placed on the parent task. The await operator only propagates one of them, so that's why you only catch one.

Flipbed

From what I can deduce the reason this occurs is that the await signals that task to wait for the task to finish. When an exception is thrown, the task is finished (since an exception crashes it) and the exception propagates outwards to your async function where it will be caught. This means that you will always catch one exception with this setup.

To always catch both, remove await and instead use Task.Factory.StartNew(..).Wait(); The Wait function will keep a count of all child processes and will not return until all of them have finished. Since multiple exceptions are thrown (one from each child) they are bundled in a new AggregateException, which later is caught and its children are flattened and the inner exceptions are printed. This should give you the output:

From 1st child
From 2nd child
** AggregateException **
ArgumentException
NullReferenceException
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!