Exception Handling
Exceptions are not terminating the program from tasks, and you can't capture any exception from it since they're behind other threads
Task task = Task.Run(() => {
throw new InvalidCastException { Source = "first task" };
});
Task task2 = Task.Run(() => {
throw new AccessViolationException { Source = "second task" };
});
// ...
// program was not terminated2
3
4
5
6
7
8
9
10
What Might be Thrown
Exception can be thrown and catched from a task for the following scenarios:
AggregateExceptioncan be thrown from:task.Wait();Task.Wait*task.Result
- Direct exception can be thrown from:
await Task.When*await tasktask.GetAwaiter().GetResult()
Catch in Statement
Exception yield from tasks is always a composite exception AggregateException unless the exception is OperationCancelledException and the task has cancelled.
NOTE
See TaskStatus.Cancelled
This particular exception has a property AggregateException.InnerExceptions to be enumerated so you can handle all exceptions delicately.
Task task = Task.Run(() => {
throw new InvalidCastException { Source = "first task" };
});
Task task2 = Task.Run(() => {
throw new AccessViolationException { Source = "second task" };
});
try {
Task.WaitAll(task, task2); // throws here
} catch (AggregateException ex) {
foreach (var iex in ex.InnerExceptions) {
if (iex is AccessViolationException) { }
}
} 2
3
4
5
6
7
8
9
10
11
12
13
14
15
Besides enumeration, AggregateException.Handle provides another way to do the similar. However, unhandled cases would still be propagated and thrown.
So, the common practice is, to handle some of the exceptions must be handled inside a method using AggregateException.Handle and leave the remained propagated outward.
try {
task.Wait();
} catch (AggregateException ex) {
ex.Handle(iex => {
switch (iex) {
case AccessViolationException:
Console.WriteLine("Handling AccessViolationException...");
return true; // true implies the handling succeeded
case InvalidCastException:
Console.WriteLine("Handling InvalidCastException...");
return true; // true implies the handling succeeded
default:
return false;
}
});
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Handle in Continued Tasks
Task object itself can hold an AggregateException as a property. So you may handle them in a continued task or a post operation.
_ = Task.Factory.StartNew(() => throw new AccessViolationException()).ContinueWith(prev => {
prev.Exception?.Handle(iex => {
if (iex is AccessViolationException) {
Console.WriteLine($"handling {nameof(AccessViolationException)}");
return true;
}
return false;
});
});2
3
4
5
6
7
8
9
Exceptions from Child Tasks
Child tasks are tasks created inside a task, the can nest really deep. All exceptions yield from such deep level would make it hard to enumerate the all exceptions from top level AggregateException since all child exceptions are a AggregateException too.
await Task.Run(() => {
try { // catch the child exception
Task.Run(() => throw new AccessViolationException())
.Wait();
} catch (AggregateException) {
throw;
}
throw new InvalidCastException();
}).ContinueWith(prev => {
prev.Exception?.Handle(iex => {
Console.WriteLine(iex is AggregateException); // True
return true;
});
});2
3
4
5
6
7
8
9
10
11
12
13
14
That's why AggregateException.Flatten() exists to flatten all AggregateException from child task into a new AggregateException with each spcific exception.
try {
await Task.Factory.StartNew(() => {
try { // catch the child exception
Task.Factory.StartNew(() => {
Task.Factory.StartNew(() => {
throw new InvalidCastException();
}, TaskCreationOptions.AttachedToParent);
throw new AccessViolationException();
}, TaskCreationOptions.AttachedToParent)
.Wait();
} catch (AggregateException) {
throw;
}
});
} catch (AggregateException ex) {
_ = ex.InnerExceptions.Count; // 2 since there's 2 child task
AggregateException flattened = ex.Flatten();
_ = flattened.InnerExceptions.Count; // 2 since each child task only yield on exception
Console.WriteLine( // AccessViolationException, InvalidCastOperation
string.Join(
", ",
ex.Flatten().InnerExceptions.Select(e => e.GetType().Name).ToArray()
)
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
From Detached Tasks
Child tasks are created in a detached way by default, the detached must rethrow its exception using any kind of wait methods or await statement.
var task = Task.Run(() => {
var child = Task.Run(() => throw new Exception("Detached child task faulted."));
child.Wait(); // throw here
});
try {
task.Wait();
} catch (AggregateException ae) {
foreach (var e in ae.Flatten().InnerExceptions)
if (e is Exception) Console.WriteLine(e.Message);
}2
3
4
5
6
7
8
9
10
11