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:
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 theNullReferenceException
or theArgumentException
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
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?
The article is wrong. When you run your code, the await
ed 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);
}