C#idiomatic C#
how-to-handle-exceptions-in-parallel-loopsstandard/parallel-programming

Async and concurrency

How to: Handle Exceptions in Parallel Loops

Learn how to handle exceptions in parallel loops in .NET. See an example of how to wrap all exceptions from the loop in a System.AggregateException.

  • page standard/parallel-programming/how-to-handle-exceptions-in-parallel-loops
  • updated 03/30/2017

The System.Threading.Tasks.Parallel.For and System.Threading.Tasks.Parallel.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); an unhandled exception causes the loop to terminate as soon as all currently running iterations finish.

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.

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.

sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_exceptions/cs/exceptions.cs08

csharp
using System.Collections.Concurrent;

public static partial class Program
{
    public static void ThrowDemo()
    {
        var task = Task.Factory.StartNew(
            () => throw new CustomException("I'm bad, but not too bad!"));

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            // Assume we know what's going on with this particular exception.
            // Rethrow anything else. AggregateException.Handle provides
            // another way to express this. See later example.
            //<snippet5>
            foreach (var ex in ae.InnerExceptions)
            {
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                else
                {
                    throw;
                }
            }
            //</snippet5>
        }
    }

    static void CancelDemo()
    {
        bool someCondition = true;
        //<snippet4>
        var tokenSource = new CancellationTokenSource();
        var token = tokenSource.Token;
        var task = Task.Factory.StartNew(() =>
        {
            CancellationToken ct = token;
            while (someCondition)
            {
                // Do some work...
                Thread.SpinWait(50_000);
                ct.ThrowIfCancellationRequested();
            }
        },
        token);

        // No waiting required.
        tokenSource.Dispose();
        //</snippet4>
    }
}

//<snippet08>
public static partial class Program
{
    public static void ExceptionTwo()
    {
        // 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[5_000];
        Random r = Random.Shared;
        r.NextBytes(data);

        try
        {
            ProcessDataInParallel(data);
        }
        catch (AggregateException ae)
        {
            var ignoredExceptions = new List<Exception>();
            // This is where you can choose which exceptions to handle.
            foreach (var ex in ae.Flatten().InnerExceptions)
            {
                if (ex is ArgumentException) Console.WriteLine(ex.Message);
                else ignoredExceptions.Add(ex);
            }
            if (ignoredExceptions.Count > 0)
            {
                throw new AggregateException(ignoredExceptions);
            }
        }

        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 < 3) throw new ArgumentException($"Value is {d}. Value must be greater than or equal to 3.");
                else Console.Write(d + " ");
            }
            // Store the exception and continue with the loop.
            catch (Exception e)
            {
                exceptions.Enqueue(e);
            }
        });
        Console.WriteLine();

        // Throw the exceptions here after the loop completes.
        if (!exceptions.IsEmpty)
        {
            throw new AggregateException(exceptions);
        }
    }
}
//</snippet08>

sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_exceptions/vb/exceptionsinloops.vb08

vb
' GOES IN TPL_Exceptions!!!
'<snippet08> 
' How to: Handle Exceptions in Parallel Loops

Imports System.Collections.Concurrent
Imports System.Collections.Generic
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
            Dim ignoredExceptions As New List(Of Exception)
            ' This is where you can choose which exceptions to handle.
            For Each ex As Exception In ae.Flatten().InnerExceptions
                If (TypeOf (ex) Is ArgumentException) Then
                    Console.WriteLine(ex.Message)
                Else
                    ignoredExceptions.Add(ex)
                End If
            Next
            If ignoredExceptions.Count > 0 Then
                Throw New AggregateException(ignoredExceptions)
            End If
        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 < 3 Then
                                                    Throw New ArgumentException($"Value is {d}. Value must be greater than or equal to 3")
                                                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)
        Console.WriteLine()
        ' Throw the exceptions here after the loop completes.
        If exceptions.Count > 0 Then
            Throw New AggregateException(exceptions)
        End If
    End Sub
End Module
'</snippet08>

See also