Dela via


Undantagshantering (aktivitetsparallellt bibliotek)

Ohanterade undantag som genereras av användarkod som körs i en aktivitet sprids tillbaka till den anropande tråden, förutom i vissa scenarier som beskrivs senare i det här avsnittet. Undantag sprids när du använder någon av de statiska metoderna eller instansmetoderna Task.Wait och du hanterar dem genom att omsluta anropet i en try/catch -instruktion. Om en aktivitet är överordnad för anslutna underordnade aktiviteter, eller om du väntar på flera aktiviteter, kan flera undantag genereras.

Om du vill sprida alla undantag tillbaka till den anropande tråden omsluter aktivitetsinfrastrukturen dem i en AggregateException instans. Undantaget AggregateException har en InnerExceptions egenskap som kan räknas upp för att undersöka alla ursprungliga undantag som utlöstes och hantera (eller inte hantera) var och en individuellt. Du kan också hantera de ursprungliga undantagen AggregateException.Handle med hjälp av metoden .

Även om endast ett undantag utlöses omsluts det fortfarande i ett AggregateException undantag, vilket visas i följande exempel.


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!
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!

Du kan undvika ett ohanterat undantag genom att bara fånga AggregateException och inte observera något av de inre undantagen. Vi rekommenderar dock att du inte gör detta eftersom det är detsamma som att fånga bastypen Exception i icke-parallella scenarier. Om du vill fånga ett undantag utan att vidta specifika åtgärder för att återställa från det kan programmet vara i ett obestämt tillstånd.

Om du inte vill anropa Task.Wait metoden för att vänta tills en aktivitet har slutförts kan du också hämta AggregateException undantaget från aktivitetens Exception egenskap, som i följande exempel visas. Mer information finns i avsnittet Observera undantag med hjälp av egenskapen Task.Exception i det här avsnittet.


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!
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!

Varning

Föregående exempelkod innehåller en while loop som avsöker aktivitetens Task.IsCompleted egenskap för att avgöra när aktiviteten har slutförts. Detta bör aldrig göras i produktionskod eftersom det är mycket ineffektivt.

Om du inte väntar på en aktivitet som sprider ett undantag eller får åtkomst till dess Exception egenskap eskaleras undantaget enligt .NET-undantagsprincipen när uppgiften samlas in.

När undantag tillåts bubbla upp igen till kopplingstråden är det möjligt att en uppgift kan fortsätta att bearbeta vissa objekt när undantaget har genererats.

Kommentar

När "Just My Code" är aktiverat bryts Visual Studio i vissa fall på raden som genererar undantaget och visar ett felmeddelande som säger "undantag hanteras inte av användarkod". Det här felet är godartat. Du kan trycka på F5 för att fortsätta och se det undantagshanteringsbeteende som visas i de här exemplen. Om du vill förhindra att Visual Studio bryter mot det första felet avmarkerar du kryssrutan Aktivera just min kod under Verktyg, Alternativ, Felsökning, Allmänt.

Bifogade underordnade uppgifter och kapslade AggregateExceptions

Om en aktivitet har en bifogad underordnad aktivitet som utlöser ett undantag, omsluts undantaget i en AggregateException innan den sprids till den överordnade aktiviteten, vilket omsluter undantaget på egen hand AggregateException innan det sprids tillbaka till den anropande tråden. I sådana fall InnerExceptions innehåller egenskapen AggregateException för undantaget som fångas på Task.Waitmetoden , WaitAnyeller WaitAll en eller AggregateException flera instanser, inte de ursprungliga undantagen som orsakade felet. Om du vill undvika att behöva iterera över kapslade AggregateException undantag kan du använda Flatten metoden för att ta bort alla kapslade AggregateException undantag, så att AggregateException.InnerExceptions egenskapen innehåller de ursprungliga undantagen. I följande exempel plattas kapslade AggregateException instanser ut och hanteras i bara en loop.


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.
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.

Du kan också använda AggregateException.Flatten metoden för att återväxa de inre undantagen från flera AggregateException instanser som genereras av flera uppgifter i en enda AggregateException instans, som följande exempel visar.

public static partial class Program
{
    public static void TaskExceptionTwo()
    {
        try
        {
            ExecuteTasks();
        }
        catch (AggregateException ae)
        {
            foreach (var e in ae.InnerExceptions)
            {
                Console.WriteLine(
                    "{0}:\n   {1}", e.GetType().Name, 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.
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.

Undantag från frånkopplade underordnade uppgifter

Som standard skapas underordnade uppgifter som frånkopplade. Undantag som genereras från frånkopplade aktiviteter måste hanteras eller återväckas i den omedelbara överordnade aktiviteten. de sprids inte tillbaka till den anropande tråden på samma sätt som kopplade underordnade uppgifter sprids tillbaka. Den överordnade överordnad kan manuellt återväxa ett undantag från ett frånkopplat underordnat objekt så att det omsluts i en AggregateException och sprids tillbaka till den anropande tråden.


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.
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.

Även om du använder en fortsättning för att observera ett undantag i en underordnad aktivitet måste undantaget fortfarande observeras av den överordnade aktiviteten.

Undantag som indikerar att kooperativet har avbrutits

När användarkod i en uppgift svarar på en begäran om annullering är rätt procedur att skicka in OperationCanceledException den annulleringstoken som begäran kommunicerades om. Innan den försöker sprida undantaget jämför aktivitetsinstansen token i undantaget med den som skickades till den när den skapades. Om de är samma sprider uppgiften en TaskCanceledException omsluten AggregateExceptioni , och det kan visas när de inre undantagen granskas. Men om den anropande tråden inte väntar på uppgiften sprids inte det här specifika undantaget. Mer information finns i Annullering av aktiviteter.

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();
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)

Använda referensmetoden för att filtrera inre undantag

Du kan använda AggregateException.Handle metoden för att filtrera bort undantag som du kan behandla som "hanterade" utan att använda någon ytterligare logik. I det användardelegat som tillhandahålls till AggregateException.Handle(Func<Exception,Boolean>) metoden kan du undersöka undantagstypen, dess Message egenskap eller annan information om den som gör att du kan avgöra om den är godartad. Eventuella undantag för vilka ombudet returnerar false återaktiveras i en ny AggregateException instans omedelbart efter att metoden har returnerats AggregateException.Handle .

Följande exempel är funktionellt likvärdigt med det första exemplet i det här avsnittet, som undersöker varje undantag i AggregateException.InnerExceptions samlingen. I stället anropar AggregateException.Handle den här undantagshanteraren metodobjektet för varje undantag och återaktiveras endast undantag som inte CustomException är instanser.


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!
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!

Följande är ett mer komplett exempel som använder AggregateException.Handle metoden för att tillhandahålla särskild hantering för ett UnauthorizedAccessException undantag vid uppräkning av filer.

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(
                    "{0}: {1}", 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(
                    "{0}: {1}", 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.
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.

Observera undantag med hjälp av egenskapen Task.Exception

Om en uppgift slutförs i TaskStatus.Faulted tillståndet kan dess Exception egenskap undersökas för att ta reda på vilket specifikt undantag som orsakade felet. Ett bra sätt att observera Exception egenskapen är att använda en fortsättning som endast körs om den föregående aktiviteten fel, som visas i följande exempel.


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

I ett meningsfullt program kan fortsättningsdelegaten logga detaljerad information om undantaget och eventuellt skapa nya uppgifter för att återställa från undantaget. Om en uppgift fel utlöser följande uttryck undantaget:

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

Använd en try-catch instruktion för att hantera och observera undantag som genereras. Du kan också observera undantaget genom att Task.Exception komma åt egenskapen.

Viktigt!

Det AggregateException går inte att uttryckligen fånga när du använder följande uttryck:

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

UnobservedTaskException-händelse

I vissa scenarier, till exempel när du är värd för ej betrodda plugin-program, kan godartade undantag vara vanliga, och det kan vara för svårt att observera dem alla manuellt. I dessa fall kan du hantera händelsen TaskScheduler.UnobservedTaskException . Den System.Threading.Tasks.UnobservedTaskExceptionEventArgs instans som skickas till hanteraren kan användas för att förhindra att det oobserverade undantaget sprids tillbaka till kopplingstråden.

Se även