4. Handling Exceptions
You can raise an exception in a parallel loop, but you should consider several factors when the need to do this occurs:
-
Parallel tasks already running are allowed to run until completion.
This means that tasks might continue to run after the unhandled
exception occurs.
-
In most circumstances, iterations not started are not allowed to run after the exception.
-
Long-running tasks should check the ParallelLoopState.IsExceptional property for pending exceptions. The property returns true when an exception is pending. If a pending exception is discovered, the task should end at the earliest convenient moment.
-
Because tasks are running in parallel, the possibility exists that
more than one exception might be raised. For that reason, the method
throws an AggregateException. You can use the AggregateException.InnerExceptions property to enumerate the underlying exceptions.
-
Unhandled exceptions within a parallel loop are caught on the
joining thread. If the parallel call was not made within the scope of a
try/catch block, the exception might cause the application to fail.
-
An unhandled exception takes precedence over a ParallelLoopState.Break or ParallelLoopState.Stop.
The following code demonstrates the proper technique to handle an
unhanded exception that occurs within a parallel loop. The code
purposely raises an unhandled exception in the fourth task. Remember,
the Parallel.For statement must be within the scope of a try block so that if an unhandled exception is raised, execution gets transferred to the joining thread—and ultimately to the catch filter, where you actually catch an AggregateException exception. Internally, the catch block enumerates the AggregateException.InnerExceptions collection and displays the unhandled exceptions.
try
{
Parallel.For(0, 6, (index) =>
{
Console.WriteLine("Task {0} started.", index);
if (index == 4)
{
throw new Exception();
}
DoSomething();
Console.WriteLine("Task {0} ended.", index);
});
}
catch (AggregateException ax)
{
Console.WriteLine("\nError List: \n");
foreach(var error in ax.InnerExceptions)
{
Console.WriteLine(error.Message);
}
}
Here’s the output from the preceding code.
5. Dealing with Dependencies
You
should strive for independent loop iterations, because that provides
the maximum parallel performance, allowing you to use embarrassingly
parallel loops. However, not every loop has perfectly independent
iterations. This is particularly true when you are porting sequential
code to parallel code, where the original developer was working under
entirely different assumptions and constraints. Dependencies, if
handled incorrectly, can cause unreliable results and, in some
circumstances, application crashes. Dependencies are typically resolved
through some degree or synchronization, which can adversely affect
performance. However, correctness is sometimes more important than
performance.