How to: Handle Exceptions in Parallel Loops
The ParallelFor() and ForEach() overloads do not have any special mechanism to handle exceptions that might be thrown. In this respect, they resemble regular for and foreach loops (For and For Each in Visual Basic).
When you add your own exception-handling logic to parallel loops, handle the case in which similar exceptions might be thrown on multiple threads concurrently, and the case in which an exception thrown on one thread causes another exception to be thrown on another thread. You can handle both cases by wrapping all exceptions from the loop in a System.AggregateException. The following example shows one possible approach.
Note
When "Just My Code" is enabled, Visual Studio in some cases will break on the line that throws the exception and display an error message that says "exception not handled by user code." This error is benign. You can press F5 to continue from it, and see the exception-handling behavior that is demonstrated in the example below. To prevent Visual Studio from breaking on the first error, just uncheck the "Just My Code" checkbox under Tools, Options, Debugging, General.
Example
In this example, all exceptions are caught and then wrapped in an System.AggregateException which is thrown. The caller can decide which exceptions to handle.
' How to: Handle Exceptions in Parallel Loops
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
Module ExceptionsInLoops
Sub Main()
' Create some random data to process in parallel.
' There is a good probability this data will cause some exceptions to be thrown.
Dim data(1000) As Byte
Dim r As New Random()
r.NextBytes(data)
Try
ProcessDataInParallel(data)
Catch ae As AggregateException
' This is where you can choose which exceptions to handle.
For Each ex As Exception In ae.InnerExceptions
If (TypeOf (ex) Is ArgumentException) Then
Console.WriteLine(ex.Message)
Else
Throw ex
End If
Next
End Try
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Sub ProcessDataInParallel(ByVal data As Byte())
' Use ConcurrentQueue to enable safe enqueueing from multiple threads.
Dim exceptions As New ConcurrentQueue(Of Exception)
' Execute the complete loop and capture all exceptions.
Parallel.ForEach(Of Byte)(data, Sub(d)
Try
' Cause a few exceptions, but not too many.
If d < &H3 Then
Throw New ArgumentException(String.Format("value is {0:x}. Element must be greater than &H3", d))
Else
Console.Write(d & " ")
End If
Catch ex As Exception
' Store the exception and continue with the loop.
exceptions.Enqueue(ex)
End Try
End Sub)
' Throw the exceptions here after the loop completes.
If exceptions.Count > 0 Then
Throw New AggregateException(exceptions)
End If
End Sub
End Module
class ExceptionDemo2
{
static void Main(string[] args)
{
// Create some random data to process in parallel.
// There is a good probability this data will cause some exceptions to be thrown.
byte[] data = new byte[5000];
Random r = new Random();
r.NextBytes(data);
try
{
ProcessDataInParallel(data);
}
catch (AggregateException ae)
{
// This is where you can choose which exceptions to handle.
foreach (var ex in ae.InnerExceptions)
{
if (ex is ArgumentException)
Console.WriteLine(ex.Message);
else
throw ex;
}
}
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
private static void ProcessDataInParallel(byte[] data)
{
// Use ConcurrentQueue to enable safe enqueueing from multiple threads.
var exceptions = new ConcurrentQueue<Exception>();
// Execute the complete loop and capture all exceptions.
Parallel.ForEach(data, d =>
{
try
{
// Cause a few exceptions, but not too many.
if (d < 0x3)
throw new ArgumentException(String.Format("value is {0:x}. Elements must be greater than 0x3.", d));
else
Console.Write(d + " ");
}
// Store the exception and continue with the loop.
catch (Exception e) { exceptions.Enqueue(e); }
});
// Throw the exceptions here after the loop completes.
if (exceptions.Count > 0) throw new AggregateException(exceptions);
}
}
See Also
Concepts
Lambda Expressions in PLINQ and TPL