8

I have this code as below to capture the exceptions throw from tasks created by using TaskFactory and Task.Run. If I use the TaskFactory, I am able to check the Exception thrown from the previous task in the Continued task without having to use the Task.WaitAll method. If I use Task.Run, the Continued task will not execute unless I explicitly Wait for the child tasks to finish. Which flag in the TaskFactory.StartNew changed this behaviour?

Also what is the difference between InnerException and InnerExceptions in AggregateException class? The InnerExceptions returns me a readonly collection of all exceptions thrown by the child tasks. The InnerException returns an AggregateExcpetion of exception thrown by only one child task.

//Use Factory
TaskCreationOptions atp = TaskCreationOptions.AttachedToParent;
Task.Factory.StartNew(() =>
{
    Task.Factory.StartNew (() => { throw null; }, atp);
    Task.Factory.StartNew (() => { throw new NullReferenceException();}, atp);
    Task.Factory.StartNew (() => { throw new Exception("Test"); }, atp);
})
.ContinueWith (p => p.Exception.Dump(),TaskContinuationOptions.OnlyOnFaulted);

//Use Task.Run
Task.Run(()=>
{
    TaskCreationOptions op = TaskCreationOptions.AttachedToParent;
    var t1 = Task.Factory.StartNew(()=> {throw null;}, op);
    var t2 = Task.Factory.StartNew(()=> {throw new NullReferenceException();}, op);
    var t3 = Task.Factory.StartNew(()=> {throw new Exception("Test");}, op);

    //This will trigger the continued task
    //Task.WaitAll(new Task[]{t1,t2,t3}); 
}).ContinueWith(task => {task.Exception.Dump();}, TaskContinuationOptions.OnlyOnFaulted);
nawfal
  • 70,104
  • 56
  • 326
  • 368
Helic
  • 907
  • 1
  • 10
  • 25

1 Answers1

16
  • InnerException is a property of Exception with returns 'the exception that caused this exception'.
  • InnerExceptions is a property unique to AggregateException. Due to its design, an aggregate exception can contain multiple 'causing' exceptions.

As the property InnerException is inherited, it makes sense that it returns the first exception from InnerExceptions.

To answer your other question re the behaviour of your sample code, the difference is the TaskCreationOptions default for Task.Run. The default is TaskCreationOptions.DenyChildAttach. You can read more about that in this blog post.

Charles Mager
  • 25,735
  • 2
  • 35
  • 45
  • when should we use InnerException as it doesn't give a full picture of what caused the issue? – Helic Jun 18 '15 at 14:59
  • 2
    I'd imagine you shouldn't if you know it's an `AggregateException`. The `InnerException` is only there to maintain the contract of `Exception`. – Charles Mager Jun 18 '15 at 15:00
  • yes, many thanks. Need to think more carefully when I use Task.Run – Helic Jun 18 '15 at 15:36
  • Generally, you [should prefer `Task.Run`](http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html). – Charles Mager Jun 18 '15 at 15:39
  • You said "it makes sense that it returns the first exception", but one should never assume that code will do what "makes sense"... Can you guarantee that this is the case? – Travis Reed Feb 08 '21 at 18:36
  • @TravisReed you can check [the source](https://github.com/dotnet/runtime/blob/7870a000fc1ec97977d2d4caeab6da885c481ecd/src/libraries/System.Private.CoreLib/src/System/AggregateException.cs#L123-L124). The first exception is passed to the base constructor. – Charles Mager Feb 10 '21 at 13:36