C#idiomatic C#
exception-handling-task-parallel-librarystandard/parallel-programming

Async and concurrency

Exception handling (Task Parallel Library)

Explore exception handling using the Task Parallel Library (TPL) in .NET. See nested aggregate exceptions, inner exceptions, unobserved task exceptions, & more.

  • page standard/parallel-programming/exception-handling-task-parallel-library
  • updated 06/08/2022

Unhandled exceptions that are thrown by user code that is running inside a task are propagated back to the calling thread, except in certain scenarios that are described later in this topic. Exceptions are propagated when you use one of the static or instance System.Threading.Tasks.Task.Wait methods, and you handle them by enclosing the call in a try/catch statement. If a task is the parent of attached child tasks, or if you are waiting on multiple tasks, multiple exceptions could be thrown.

To propagate all the exceptions back to the calling thread, the Task infrastructure wraps them in an System.AggregateException instance. The System.AggregateException exception has an System.AggregateException.InnerExceptions property that can be enumerated to examine all the original exceptions that were thrown, and handle (or not handle) each one individually. You can also handle the original exceptions by using the System.AggregateException.Handle method.

Even if only one exception is thrown, it is still wrapped in an System.AggregateException exception, as the following example shows.

sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_exceptions/cs/handling21.cs21

csharp
//<snippet21>

public static partial class Program
{
    public static void HandleThree()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
            {
                // Handle the custom exception.
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                // Rethrow any other exception.
                else
                {
                    throw ex;
                }
            }
        }
    }
}
// The example displays the following output:
//        This exception is expected!
//</snippet21>

sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_exceptions/vb/handling21.vb21

vb
' Visual Basic .NET Document
Option Strict On

' <Snippet21>
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

        Try
            task1.Wait()
        Catch ae As AggregateException
            For Each ex In ae.InnerExceptions
                ' Handle the custom exception.
                If TypeOf ex Is CustomException Then
                    Console.WriteLine(ex.Message)
                    ' Rethrow any other exception.
                Else
                    Throw ex
                End If
            Next
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       This exception is expected!
' </Snippet21>

You could avoid an unhandled exception by just catching the System.AggregateException and not observing any of the inner exceptions. However, we recommend that you do not do this because it is analogous to catching the base System.Exception type in non-parallel scenarios. To catch an exception without taking specific actions to recover from it can leave your program in an indeterminate state.

If you do not want to call the System.Threading.Tasks.Task.Wait method to wait for a task's completion, you can also retrieve the System.AggregateException exception from the task's System.Threading.Tasks.Task.Exception property, as the following example shows. For more information, see the Observing exceptions by using the Task.Exception property section in this topic.

sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_exceptions/cs/handling22.cs29

csharp
//<snippet29>

public static partial class Program
{
    public static void HandleFour()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        while (!task.IsCompleted) { }

        if (task.Status == TaskStatus.Faulted)
        {
            foreach (var ex in task.Exception?.InnerExceptions ?? new(Array.Empty<Exception>()))
            {
                // Handle the custom exception.
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                // Rethrow any other exception.
                else
                {
                    throw ex;
                }
            }
        }
    }
}
// The example displays the following output:
//        This exception is expected!
//</snippet29>

sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_exceptions/vb/handling22.vb29

vb
' Visual Basic .NET Document
Option Strict On

' <Snippet29>
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

        While Not task1.IsCompleted
        End While

        If task1.Status = TaskStatus.Faulted Then
            For Each ex In task1.Exception.InnerExceptions
                ' Handle the custom exception.
                If TypeOf ex Is CustomException Then
                    Console.WriteLine(ex.Message)
                    ' Rethrow any other exception.
                Else
                    Throw ex
                End If
            Next
        End If
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       This exception is expected!
' </Snippet29>

If you do not wait on a task that propagates an exception, or access its System.Threading.Tasks.Task.Exception property, the exception is escalated according to the .NET exception policy when the task is garbage-collected.

When exceptions are allowed to bubble up back to the joining thread, it is possible that a task may continue to process some items after the exception is raised.

Attached child tasks and nested AggregateExceptions

If a task has an attached child task that throws an exception, that exception is wrapped in an System.AggregateException before it is propagated to the parent task, which wraps that exception in its own System.AggregateException before it propagates it back to the calling thread. In such cases, the System.AggregateException.InnerExceptions property of the System.AggregateException exception that is caught at the System.Threading.Tasks.Task.Wait, System.Threading.Tasks.Task.WaitAny, or System.Threading.Tasks.Task.WaitAll method contains one or more System.AggregateException instances, not the original exceptions that caused the fault. To avoid having to iterate over nested System.AggregateException exceptions, you can use the System.AggregateException.Flatten method to remove all the nested System.AggregateException exceptions, so that the System.AggregateException.InnerExceptions property contains the original exceptions. In the following example, nested System.AggregateException instances are flattened and handled in just one loop.

sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_exceptions/cs/flatten2.cs22

csharp
// <Snippet22>

public static partial class Program
{
    public static void FlattenTwo()
    {
        var task = Task.Factory.StartNew(() =>
        {
            var child = Task.Factory.StartNew(() =>
            {
                var grandChild = Task.Factory.StartNew(() =>
                {
                    // This exception is nested inside three AggregateExceptions.
                    throw new CustomException("Attached child2 faulted.");
                }, TaskCreationOptions.AttachedToParent);

                // This exception is nested inside two AggregateExceptions.
                throw new CustomException("Attached child1 faulted.");
            }, TaskCreationOptions.AttachedToParent);
        });

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.Flatten().InnerExceptions)
            {
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                else
                {
                    throw;
                }
            }
        }
    }
}
// The example displays the following output:
//    Attached child1 faulted.
//    Attached child2 faulted.
// </Snippet22>

sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_exceptions/vb/flatten2.vb22

vb
' Visual Basic .NET Document
Option Strict On

' <Snippet22>
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Factory.StartNew(Sub()
                                              Dim child1 = Task.Factory.StartNew(Sub()
                                                                                     Dim child2 = Task.Factory.StartNew(Sub()
                                                                                                                            Throw New CustomException("Attached child2 faulted.")
                                                                                                                        End Sub,
                                                                                                                        TaskCreationOptions.AttachedToParent)
                                                                                     Throw New CustomException("Attached child1 faulted.")
                                                                                 End Sub,
                                                                                 TaskCreationOptions.AttachedToParent)
                                          End Sub)

        Try
            task1.Wait()
        Catch ae As AggregateException
            For Each ex In ae.Flatten().InnerExceptions
                If TypeOf ex Is CustomException Then
                    Console.WriteLine(ex.Message)
                Else
                    Throw
                End If
            Next
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       Attached child1 faulted.
'       Attached child2 faulted.
' </Snippet22>

You can also use the System.AggregateException.Flatten method to rethrow the inner exceptions from multiple System.AggregateException instances thrown by multiple tasks in a single System.AggregateException instance, as the following example shows.

sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_exceptions/cs/taskexceptions2.cs13

csharp
// <snippet13>
public static partial class Program
{
    public static void TaskExceptionTwo()
    {
        try
        {
            ExecuteTasks();
        }
        catch (AggregateException ae)
        {
            foreach (var e in ae.InnerExceptions)
            {
                Console.WriteLine($"{e.GetType().Name}:\n   {e.Message}");
            }
        }
    }

    static void ExecuteTasks()
    {
        // Assume this is a user-entered String.
        string path = @"C:\";
        List<Task> tasks = new();

        tasks.Add(Task.Run(() =>
        {
            // This should throw an UnauthorizedAccessException.
            return Directory.GetFiles(
                path, "*.txt",
                SearchOption.AllDirectories);
        }));

        tasks.Add(Task.Run(() =>
        {
            if (path == @"C:\")
            {
                throw new ArgumentException(
                    "The system root is not a valid path.");
            }
            return new string[] { ".txt", ".dll", ".exe", ".bin", ".dat" };
        }));

        tasks.Add(Task.Run(() =>
        {
            throw new NotImplementedException(
                "This operation has not been implemented.");
        }));

        try
        {
            Task.WaitAll(tasks.ToArray());
        }
        catch (AggregateException ae)
        {
            throw ae.Flatten();
        }
    }
}
// The example displays the following output:
//       UnauthorizedAccessException:
//          Access to the path 'C:\Documents and Settings' is denied.
//       ArgumentException:
//          The system root is not a valid path.
//       NotImplementedException:
//          This operation has not been implemented.
// </Snippet13>

sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_exceptions/vb/taskexceptions2.vb13

vb
'<snippet13>
Imports System.Collections.Generic
Imports System.IO
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Try
            ExecuteTasks()
        Catch ae As AggregateException
            For Each e In ae.InnerExceptions
                Console.WriteLine("{0}:{2}   {1}", e.GetType().Name, e.Message,
                                  vbCrLf)
            Next
        End Try
    End Sub

    Sub ExecuteTasks()
        ' Assume this is a user-entered String.
        Dim path = "C:\"
        Dim tasks As New List(Of Task)

        tasks.Add(Task.Run(Function()
                               ' This should throw an UnauthorizedAccessException.
                               Return Directory.GetFiles(path, "*.txt",
                                                         SearchOption.AllDirectories)
                           End Function))

        tasks.Add(Task.Run(Function()
                               If path = "C:\" Then
                                   Throw New ArgumentException("The system root is not a valid path.")
                               End If
                               Return {".txt", ".dll", ".exe", ".bin", ".dat"}
                           End Function))

        tasks.Add(Task.Run(Sub()
                               Throw New NotImplementedException("This operation has not been implemented.")
                           End Sub))

        Try
            Task.WaitAll(tasks.ToArray)
        Catch ae As AggregateException
            Throw ae.Flatten()
        End Try
    End Sub
End Module
' The example displays the following output:
'       UnauthorizedAccessException:
'          Access to the path 'C:\Documents and Settings' is denied.
'       ArgumentException:
'          The system root is not a valid path.
'       NotImplementedException:
'          This operation has not been implemented.
' </snippet13>

Exceptions from detached child tasks

By default, child tasks are created as detached. Exceptions thrown from detached tasks must be handled or rethrown in the immediate parent task; they are not propagated back to the calling thread in the same way as attached child tasks propagated back. The topmost parent can manually rethrow an exception from a detached child to cause it to be wrapped in an System.AggregateException and propagated back to the calling thread.

sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_exceptions/cs/detached21.cs23

csharp
// <Snippet23>

public static partial class Program
{
    public static void DetachedTwo()
    {
        var task = Task.Run(() =>
        {
            var nestedTask = Task.Run(
                () => throw new CustomException("Detached child task faulted."));

            // Here the exception will be escalated back to the calling thread.
            // We could use try/catch here to prevent that.
            nestedTask.Wait();
        });

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var e in ae.Flatten().InnerExceptions)
            {
                if (e is CustomException)
                {
                    Console.WriteLine(e.Message);
                }
            }
        }
    }
}
// The example displays the following output:
//    Detached child task faulted.
// </snippet23>

sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_exceptions/vb/detached21.vb23

vb
' Visual Basic .NET Document
Option Strict On

' <Snippet23>
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub()
                                 Dim nestedTask1 = Task.Run(Sub()
                                                                Throw New CustomException("Detached child task faulted.")
                                                            End Sub)
                                 ' Here the exception will be escalated back to joining thread.
                                 ' We could use try/catch here to prevent that.
                                 nestedTask1.Wait()
                             End Sub)

        Try
            task1.Wait()
        Catch ae As AggregateException
            For Each ex In ae.Flatten().InnerExceptions
                If TypeOf ex Is CustomException Then
                    ' Recover from the exception. Here we just
                    ' print the message for demonstration purposes.
                    Console.WriteLine(ex.Message)
                End If
            Next
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       Detached child task faulted.
' </Snippet23>

Even if you use a continuation to observe an exception in a child task, the exception still must be observed by the parent task.

Exceptions that indicate cooperative cancellation

When user code in a task responds to a cancellation request, the correct procedure is to throw an System.OperationCanceledException passing in the cancellation token on which the request was communicated. Before it attempts to propagate the exception, the task instance compares the token in the exception to the one that was passed to it when it was created. If they are the same, the task propagates a System.Threading.Tasks.TaskCanceledException wrapped in the System.AggregateException, and it can be seen when the inner exceptions are examined. However, if the calling thread is not waiting on the task, this specific exception will not be propagated. For more information, see Task Cancellation.

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

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/tpl_exceptions.vb4

vb
Option Strict On
Option Explicit On
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Collections.Concurrent

Module Module1

    Class MyCustomException
        Inherits Exception
        Public Sub New(ByVal s As String)
            MyBase.New(s)
        End Sub


    End Class
    Class Program

        Public Shared Sub Main()
            '  ThrowDemo()
            DetachedTasks()
            Console.ReadKey()
        End Sub
        Shared Sub ThrowDemo()
            '<snippet1>
            Dim task1 = Task.Factory.StartNew(Sub()
                                                  Throw New MyCustomException("I'm bad, but not too bad!")
                                              End Sub)

            Try
                task1.Wait()
            Catch ae As AggregateException
                ' 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>
                For Each ex In ae.InnerExceptions
                    If TypeOf (ex) Is MyCustomException Then
                        Console.WriteLine(ex.Message)
                    Else
                        Throw
                    End If
                Next
                '</snippet5>

            End Try
            '</snippet1>

        End Sub

        Shared Sub Children()
            '<snippet2>
            ' task1 will throw an AE inside an AE inside an AE
            Dim task1 = Task.Factory.StartNew(Sub()
                                                  Dim child1 = Task.Factory.StartNew(Sub()
                                                                                         Dim child2 = Task.Factory.StartNew(Sub()
                                                                                                                                Throw New MyCustomException("Attached child2 faulted.")
                                                                                                                            End Sub,
                                                                                                                            TaskCreationOptions.AttachedToParent)
                                                                                     End Sub,
                                                                                     TaskCreationOptions.AttachedToParent)
                                                  ' Uncomment this line to see the exception rethrown.
                                                  ' throw new MyCustomException("Attached child1 faulted.")
                                              End Sub)
            Try
                task1.Wait()
            Catch ae As AggregateException
                For Each ex In ae.Flatten().InnerExceptions
                    If TypeOf (ex) Is MyCustomException Then
                        Console.WriteLine(ex.Message)
                    Else
                        Throw
                    End If
                Next
                'or like this:
                '  ae.Flatten().Handle(Function(e)
                '                               Return TypeOf (e) Is MyCustomException
                '                   End Function)
            End Try
            '</snippet2>
            Console.ReadKey()
        End Sub

        Shared Sub DetachedTasks()
            '<snippet3>
            Dim task1 = Task.Factory.StartNew(Sub()
                                                  Dim nestedTask1 = Task.Factory.StartNew(Sub()
                                                                                              Throw New MyCustomException("Nested task faulted.")
                                                                                          End Sub)
                                                  ' Here the exception will be escalated back to joining thread.
                                                  ' We could use try/catch here to prevent that.
                                                  nestedTask1.Wait()
                                              End Sub)
            Try
                task1.Wait()
            Catch ae As AggregateException
                For Each ex In ae.Flatten().InnerExceptions
                    If TypeOf (ex) Is MyCustomException Then
                        ' Recover from the exception. Here we just
                        ' print the message for demonstration purposes.
                        Console.WriteLine(ex.Message)
                    End If
                Next
            End Try
            '</snippet3>
            Console.ReadKey()
        End Sub

        Shared Sub CancelDemo()
            '<snippet4>
            Dim someCondition As Boolean = True
            Dim tokenSource = New CancellationTokenSource()
            Dim token = tokenSource.Token

            Dim task1 = Task.Factory.StartNew(Sub()
                                                  Dim ct As CancellationToken = token
                                                  While someCondition = True
                                                      ' Do some work...
                                                      Thread.SpinWait(500000)
                                                      ct.ThrowIfCancellationRequested()
                                                  End While
                                              End Sub,
                                              token)
            '</snippet4>
            tokenSource.Dispose()
        End Sub

        Shared Sub Handle(ByVal ae As AggregateException)
            '<snippet6>
            ae.Handle(Function(ex)
                          Return TypeOf (ex) Is MyCustomException
                      End Function)
            '</snippet6>
        End Sub

        Shared Sub ExceptionProperty()
            '<snippet7>
            Dim task1 = Task.Factory.StartNew(Sub()
                                                  Throw New MyCustomException("task1 faulted.")
                                              End Sub).ContinueWith(Sub(t)
                                                                        Console.WriteLine("I have observed a {0}", _
                                                                                          t.Exception.InnerException.GetType().Name)
                                                                    End Sub,
                                                                    TaskContinuationOptions.OnlyOnFaulted)
            '</snippet7>
        End Sub

    End Class

End Module

Using the handle method to filter inner exceptions

You can use the System.AggregateException.Handle method to filter out exceptions that you can treat as "handled" without using any further logic. In the user delegate that is supplied to the System.AggregateException.Handle%28System.Func%7BSystem.Exception%2CSystem.Boolean%7D%29 method, you can examine the exception type, its System.Exception.Message property, or any other information about it that will let you determine whether it is benign. Any exceptions for which the delegate returns false are rethrown in a new System.AggregateException instance immediately after the System.AggregateException.Handle method returns.

The following example is functionally equivalent to the first example in this topic, which examines each exception in the System.AggregateException.InnerExceptions collection. Instead, this exception handler calls the System.AggregateException.Handle method object for each exception, and only rethrows exceptions that are not CustomException instances.

sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_exceptions/cs/handlemethod21.cs26

csharp
// <Snippet26>

public static partial class Program
{
    public static void HandleMethodThree()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            // Call the Handle method to handle the custom exception,
            // otherwise rethrow the exception.
            ae.Handle(ex =>
            {
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                return ex is CustomException;
            });
        }
    }
}
// The example displays the following output:
//        This exception is expected!
// </Snippet26>

sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_exceptions/vb/handlemethod21.vb26

vb
' Visual Basic .NET Document
Option Strict On

' <Snippet26>
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

        Try
            task1.Wait()
        Catch ae As AggregateException
            ' Call the Handle method to handle the custom exception,
            ' otherwise rethrow the exception.
            ae.Handle(Function(e)
                          If TypeOf e Is CustomException Then
                              Console.WriteLine(e.Message)
                          End If
                          Return TypeOf e Is CustomException
                      End Function)
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       This exception is expected!
' </Snippet26>

The following is a more complete example that uses the System.AggregateException.Handle method to provide special handling for an System.UnauthorizedAccessException exception when enumerating files.

sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_exceptions/cs/taskexceptions.cs12

csharp
// <snippet12>
public static partial class Program
{
    public static void TaskException()
    {
        // This should throw an UnauthorizedAccessException.
        try
        {
            if (GetAllFiles(@"C:\") is { Length: > 0 } files)
            {
                foreach (var file in files)
                {
                    Console.WriteLine(file);
                }
            }
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
            {
                Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
            }
        }
        Console.WriteLine();

        // This should throw an ArgumentException.
        try
        {
            foreach (var s in GetAllFiles(""))
            {
                Console.WriteLine(s);
            }
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
                Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
        }
    }

    static string[] GetAllFiles(string path)
    {
        var task1 =
            Task.Run(() => Directory.GetFiles(
                path, "*.txt",
                SearchOption.AllDirectories));

        try
        {
            return task1.Result;
        }
        catch (AggregateException ae)
        {
            ae.Handle(x =>
            {
                // Handle an UnauthorizedAccessException
                if (x is UnauthorizedAccessException)
                {
                    Console.WriteLine(
                        "You do not have permission to access all folders in this path.");
                    Console.WriteLine(
                        "See your network administrator or try another path.");
                }
                return x is UnauthorizedAccessException;
            });
            return Array.Empty<string>();
        }
    }
}
// The example displays the following output:
//       You do not have permission to access all folders in this path.
//       See your network administrator or try another path.
//
//       ArgumentException: The path is not of a legal form.
// </snippet12>

sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_exceptions/vb/taskexceptions.vb12

vb
' <snippet12>
Imports System.IO
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        ' This should throw an UnauthorizedAccessException.
        Try
            Dim files = GetAllFiles("C:\")
            If files IsNot Nothing Then
                For Each file In files
                    Console.WriteLine(file)
                Next
            End If
        Catch ae As AggregateException
            For Each ex In ae.InnerExceptions
                Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
            Next
        End Try
        Console.WriteLine()

        ' This should throw an ArgumentException.
        Try
            For Each s In GetAllFiles("")
                Console.WriteLine(s)
            Next
        Catch ae As AggregateException
            For Each ex In ae.InnerExceptions
                Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
            Next
        End Try
        Console.WriteLine()
    End Sub

    Function GetAllFiles(ByVal path As String) As String()
        Dim task1 = Task.Run(Function()
                                 Return Directory.GetFiles(path, "*.txt",
                                                           SearchOption.AllDirectories)
                             End Function)
        Try
            Return task1.Result
        Catch ae As AggregateException
            ae.Handle(Function(x)
                          ' Handle an UnauthorizedAccessException
                          If TypeOf x Is UnauthorizedAccessException Then
                              Console.WriteLine("You do not have permission to access all folders in this path.")
                              Console.WriteLine("See your network administrator or try another path.")
                          End If
                          Return TypeOf x Is UnauthorizedAccessException
                      End Function)
        End Try
        Return Array.Empty(Of String)()
    End Function
End Module
' The example displays the following output:
'       You do not have permission to access all folders in this path.
'       See your network administrator or try another path.
'
'       ArgumentException: The path is not of a legal form.
' </snippet12>

Observing exceptions by using the Task.Exception property

If a task completes in the System.Threading.Tasks.TaskStatus.Faulted state, its System.Threading.Tasks.Task.Exception property can be examined to discover which specific exception caused the fault. A good way to observe the System.Threading.Tasks.Task.Exception property is to use a continuation that runs only if the antecedent task faults, as shown in the following example.

sample · csharpsamples/snippets/csharp/VS_Snippets_Misc/tpl_exceptions/cs/exceptionprop21.cs27

csharp
// <Snippet27>

public static partial class Program
{
    public static void ExceptionPropagationTwo()
    {
        _ = Task.Run(
            () => throw new CustomException("task1 faulted."))
            .ContinueWith(_ =>
            {
                if (_.Exception?.InnerException is { } inner)
                {
                    Console.WriteLine($"{inner.GetType().Name}: {inner.Message}");
                }
            }, 
            TaskContinuationOptions.OnlyOnFaulted);
        
        Thread.Sleep(500);
    }
}
// The example displays output like the following:
//        CustomException: task1 faulted.
// </Snippet27>

sample · vbsamples/snippets/visualbasic/VS_Snippets_Misc/tpl_exceptions/vb/exceptionprop21.vb27

vb
' Visual Basic .NET Document
Option Strict On

' <Snippet27>
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Factory.StartNew(Sub()
                                              Throw New CustomException("task1 faulted.")
                                          End Sub).
                    ContinueWith(Sub(t)
                                     Console.WriteLine("{0}: {1}",
                                                     t.Exception.InnerException.GetType().Name,
                                                     t.Exception.InnerException.Message)
                                 End Sub, TaskContinuationOptions.OnlyOnFaulted)

        Thread.Sleep(500)
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays output like the following:
'       CustomException: task1 faulted.
' </Snippet27>

In a meaningful application, the continuation delegate could log detailed information about the exception and possibly spawn new tasks to recover from the exception. If a task faults, the following expressions throw the exception:

  • await task
  • task.Wait()
  • task.Result
  • task.GetAwaiter().GetResult()

Use a try-catch statement to handle and observe thrown exceptions. Alternatively, observe the exception by accessing the System.Threading.Tasks.Task.Exception property.

UnobservedTaskException event

In some scenarios, such as when hosting untrusted plug-ins, benign exceptions might be common, and it might be too difficult to manually observe them all. In these cases, you can handle the System.Threading.Tasks.TaskScheduler.UnobservedTaskException event. The System.Threading.Tasks.UnobservedTaskExceptionEventArgs instance that is passed to your handler can be used to prevent the unobserved exception from being propagated back to the joining thread.

See also