3

Running the following C# console app

class Program
{  static void Main(string[] args)
   {  Tst();
      Console.ReadLine();
   }
   async static Task  Tst()
   {
       try
       {
           await Task.Factory.StartNew
             (() =>
                {
                   Task.Factory.StartNew
                       (() =>
                         { throw new NullReferenceException(); }
                         , TaskCreationOptions.AttachedToParent
                        );
               Task.Factory.StartNew
                       (  () =>
                               { throw new ArgumentException(); }
                               ,TaskCreationOptions.AttachedToParent
                       );
                }
             );
    }
    catch (AggregateException ex)
    {
        // this catch will never be target
        Console.WriteLine("** {0} **", ex.GetType().Name);

//******  Update1 - Start of Added code
        foreach (var exc in ex.Flatten().InnerExceptions)
        {
             Console.WriteLine(exc.GetType().Name);
        }
//******  Update1 - End of Added code
    }
    catch (Exception ex)
    {
       Console.WriteLine("## {0} ##", ex.GetType().Name);
    }
 }

produces the output:

** AggregateException **

Though, the code above is reproducing the first snippet from "Async - Handling multiple Exceptions" blog article, which tells about it :

the following code will catch a single NullReferenceException or ArgumentException exception (the AggregateException will be ignored)

Where is the problem:

  1. the article is wrong?
    Which code/statements and how to change in order to correctly understand it?
  2. I made an error in reproducing the first code snippet of the article?
  3. It is due to a bug in .NET 4.0/VS2010 Async CTP extension, I am using?

Update1 (in response to svick's answer)

Upon adding the code

//******  Update1 - Start of Added code
        foreach (var exc in ex.Flatten().InnerExceptions)
        {
             Console.WriteLine(exc.GetType().Name);
        }
//******  Update1 - End of Added code

the produced output is:

** AggregateException **
NullReferenceException

So, as also commented Matt Smith:

the AggregateException that is caught, contains only one of the exceptions that was thrown (either the NullReferenceException or the ArgumentException depending on the order of execution of the child Tasks)

Most probably, the article is still correct or, at least, very useful. I just need to understand how to better read/understand/use it

Update2 (in response to svick's answer)

Executing svick's code:

internal class Program
{
  private static void Main(string[] args)
  {
    Tst();
    Console.ReadLine();
  }

  private static async Task Tst()
  {
    try
    {
      await TaskEx.WhenAll
        (
          Task.Factory.StartNew
            (() =>
               { throw new NullReferenceException(); }
            //, TaskCreationOptions.AttachedToParent
            ),
          Task.Factory.StartNew
            (() =>
               { throw new ArgumentException(); }
            //,TaskCreationOptions.AttachedToParent
            )

        );
    }
    catch (AggregateException ex)
    {
      // this catch will never be target
      Console.WriteLine("** {0} **", ex.GetType().Name);

      //******  Update1 - Start of Added code
      foreach (var exc in ex.Flatten().InnerExceptions)
      {
        Console.WriteLine("==="+exc.GetType().Name);
      }
      //******  Update1 - End of Added code
    }
    catch (Exception ex)
    {
      Console.WriteLine("## {0} ##", ex.GetType().Name);
    }
  }
}

produces:

## NullReferenceException ##

output.

Why isn't AggregateException produced or caught?

Community
  • 1
  • 1
  • 2
    I see the same results with VS2012 and .Net4.5. I'm pretty sure the article is just wrong. Using `AttachedToParent` means that exceptions will be automatically propogated (even without an `await`, `Wait()`, `.Result` call) but the parent `Task` will still throw an `AggregateException` whose inner exception(s) correspond to the actual exception(s) thrown. – dlev May 15 '13 at 18:01
  • @dlev , thanks. Could you copy-paste your comment into the answer section? I am thinking to post the question about differences of exceptions propagation between child and detached nested tasks – Gennady Vanin Геннадий Ванин May 15 '13 at 18:14
  • 1
    Note, dlev's comment is slightly misleading: If you do `Wait` or `Result` you'll get the outer `AggregateException` which does contain all the exceptions. If you do `await`, you'll get one of the inner `AggregateException`s which represents the exceptions thrown by only *one* of the child `Task`s. – Matt Smith May 15 '13 at 19:05
  • @MattSmith, I'd appreciate if you put your comments as the answer – Gennady Vanin Геннадий Ванин May 16 '13 at 04:23
  • #dlev, just checked after exiting from my hibernation (sleeping) -without `await` no exception is caught (but with `await` only 1 of 2 is caught) – Gennady Vanin Геннадий Ванин May 16 '13 at 05:03
  • @Геннадий-Ванин, my comment was after svick's answer was posted (which I think is the correct answer). – Matt Smith May 16 '13 at 13:13

2 Answers2

6

The article is wrong. When you run your code, the awaited Task contains an exception that looks something like this:

AggregateException
  AggregateException
    NullReferenceException
  AggregateException
    ArgumentException

What await (or, more specifically, TaskAwaiter.GetResult()) does here is that it takes the outer AggregateException and rethrows its first child exception. Here, that's another AggregateException, so that's what is thrown.

Example of code where a Task has multiple exceptions and one of them is directly rethrown after await would be to use Task.WhenAll() instead of AttachedToParent:

try
{
    await Task.WhenAll(
        Task.Factory.StartNew(() => { throw new NullReferenceException(); }),
        Task.Factory.StartNew(() => { throw new ArgumentException(); }));
}
catch (AggregateException ex)
{
    // this catch will never be target
    Console.WriteLine("** {0} **", ex.GetType().Name);
}
catch (Exception ex)
{
    Console.WriteLine("## {0} ##", ex.GetType().Name);
}
svick
  • 236,525
  • 50
  • 385
  • 514
  • 2
    +1, To highlight: the `AggregateException` that is caught, contains only *one* of the exceptions that was thrown (either the `NullReferenceException` or the `ArgumentException` depending on the order of execution of the child `Tasks`) – Matt Smith May 15 '13 at 19:02
  • @svick , your starting statements are not correct (see Update1 in my question) and I have very-very bad idiosyncrasy of not proceeding further until I understand how to correct the wrong or correctly / better understand (disambiguate) incorrect statements (and why they were at all and what they were supposed to convey) – Gennady Vanin Геннадий Ванин May 16 '13 at 04:30
  • @MattSmith, it is independendent on " on the order of execution of the child Tasks" but what I have the difficulty to grasp [why only one exception from child tasks always propagated?](http://stackoverflow.com/questions/16580125/why-is-only-one-from-many-exceptions-from-child-tasks-always-propagated) – Gennady Vanin Геннадий Ванин May 16 '13 at 06:10
  • 2
    @Геннадий-Ванин Your update is not in contradiction with my answer. Like I said, the exception that is thrown by `await` is not the outer `AggregateException`, it's first of the inner ones. So, calling `Flatten()` on the caught exception returning only one exception is excepted. But if you save the `Task` into a variable and call `task.Exception.Flatten()`, that will return both exceptions. – svick May 16 '13 at 11:45
2

In response to your "Update 2", the reasoning is still the same as in svick's answer. The task contains an AggregateException, but awaiting it throws the first InnerException.

The additional information you need is in the Task.WhenAll documentation (emphasis mine):

If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state, where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.

So that Task's exceptions will look like:

AggregateException 
    NullReferenceException 
    ArgumentException
Matt Smith
  • 17,026
  • 7
  • 53
  • 103
  • So, why then `AggreagateException` is never caught? Are exceptions really wrapped into `AggreagateException` for detached tasks of Update2? The code in `catch (AggregateException ex){} `is never entered. The exception is caught only in second `catch (Exception ex) {}` block (and without it, is lost) – Gennady Vanin Геннадий Ванин May 17 '13 at 16:28
  • 1
    Because the AggregateException is never thrown. When a task ends up in the faulted state, using `await` on it will do the following: 1. Get the Task's `AggregateException` (it is always an `AggregateException`) 2. take the first `InnerException` and throw it. – Matt Smith May 17 '13 at 16:36