Share via


Ausnahmebehandlung (Task Parallel Library)

Nicht behandelte Ausnahmen, die von innerhalb einer Aufgabe ausgeführtem Benutzercode ausgelöst werden, werden zurück zum Verbindungsthread geleitet. Hiervon ausgenommen sind bestimmte Szenarios, die weiter unten in diesem Thema beschrieben werden. Ausnahmen werden weitergegeben, wenn Sie die statische oder instanzbasierte Task.Wait-Methode oder Task<TResult>.Wait-Methode verwenden. Zur Behandlung dieser Ausnahmen schließen Sie den Aufruf in eine try-catch-Anweisung ein. Wenn eine Aufgabe das übergeordnete Element angefügter untergeordneter Aufgaben ist oder wenn Sie auf mehrere Aufgaben warten, können mehrere Ausnahmen ausgelöst werden. Um alle Ausnahmen zurück an den aufrufenden Thread zu leiten, schließt die Aufgabeninfrastruktur diese in eine AggregateException-Instanz ein. AggregateException verfügt über eine InnerExceptions-Eigenschaft, die aufgelistet werden kann, um alle ursprünglich ausgelösten Ausnahmen zu analysieren und jede Ausnahme einzeln zu behandeln (bzw. nicht zu behandeln). Auch wenn nur eine Ausnahme ausgelöst wurde, wird diese in eine AggregateException eingeschlossen.

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.
    For Each ex In ae.InnerExceptions
        If TypeOf (ex) Is MyCustomException Then
            Console.WriteLine(ex.Message)
        Else
            Throw
        End If
    Next

End Try
var task1 = Task.Factory.StartNew(() =>
{
    throw new MyCustomException("I'm bad, but not too bad!");
});

try
{
    task1.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.
    foreach (var e in ae.InnerExceptions)
    {
        if (e is MyCustomException)
        {
            Console.WriteLine(e.Message);
        }
        else
        {
            throw;
        }
    }

}

Unbehandeltes Ausnahmen vermeiden Sie, indem Sie nur die AggregateException erfassen und die inneren Ausnahmen nicht beachten. Hiervon wird jedoch abgeraten, da dies dem Erfassen des Basisausnahmetyps in nicht parallelen Szenarios entspricht. Wenn Sie eine Ausnahme erfassen, ohne Maßnahmen zu deren Behandlung zu ergreifen, weist das Programm ggf. einen unbestimmten Zustand auf.

Wenn Sie nicht auf eine Aufgabe warten, die eine Ausnahme weitergibt, oder auf deren Exception-Eigenschaft zugreifen, wird die Ausnahme entsprechend der .NET-Ausnahmerichtlinie eskaliert, sofern die Aufgabe der Garbage Collection unterliegt.

Wenn Ausnahmen mittels Bubbling wieder an den Verbindungsthread übergeben werden können, ist es möglich, dass eine Aufgabe nach dem Auslösen der Ausnahme weiterhin einige Elemente verarbeitet.

HinweisHinweis

Wenn "Nur eigenen Code" aktiviert ist, unterbricht Visual Studio die Ausführung in einigen Fällen in der Zeile, die die Ausnahme auslöst, und eine Fehlermeldung zu einer nicht vom Benutzercode behandelten Ausnahme wird angezeigt. Dieser Fehler hat keine Auswirkungen.Sie können F5 drücken, um den Vorgang fortzusetzen. In diesem Fall wird das in den folgenden Beispielen veranschaulichte Ausnahmebehandlungsverhalten angewendet.Um zu verhindern, dass Visual Studio die Ausführung beim ersten Fehler unterbricht, deaktivieren Sie das Kontrollkästchen "Nur eigenen Code" unter Extras, Optionen, Debugging, Allgemein.

Angefügte untergeordnete Aufgaben und geschachtelte AggregateExceptions

Wenn eine Aufgabe über eine angefügte untergeordnete Aufgabe verfügt, die eine Ausnahme auslöst, wird diese Ausnahme in AggregateException eingeschlossen, bevor sie an die übergeordnete Aufgabe weitergegeben wird, die diese Ausnahme in eine eigene AggregateException einschließt und sie anschließend an den aufrufenden Thread zurückgibt. In solchen Fällen enthält die AggregateException().InnerExceptions-Eigenschaft der AggregateException, die bei der Methode Task.Wait, Task<TResult>.Wait, WaitAny oder WaitAll abgefangen wird, eine oder mehrere AggregateException-Instanzen und nicht die ursprünglichen Ausnahmen, die den Fehler verursacht haben. Um eine Iteration geschachtelter AggregateExceptions zu vermeiden, können Sie mit der Flatten()-Methode alle geschachtelten AggregateExceptions entfernen, sodass die AggregateException() InnerExceptions-Eigenschaft die ursprünglichen Ausnahmen enthält. Im folgenden Beispiel werden geschachtelte AggregateException-Instanzen vereinfacht und in nur einen Schleife behandelt.

' 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
// task1 will throw an AE inside an AE inside an AE
var task1 = Task.Factory.StartNew(() =>
{
    var child1 = Task.Factory.StartNew(() =>
        {
            var child2 = Task.Factory.StartNew(() =>
            {
                throw new MyCustomException("Attached child2 faulted.");
            },
            TaskCreationOptions.AttachedToParent);

            // Uncomment this line to see the exception rethrown.
            // throw new MyCustomException("Attached child1 faulted.");
        },
        TaskCreationOptions.AttachedToParent);
});

try
{
    task1.Wait();
}
catch (AggregateException ae)
{

    foreach (var e in ae.Flatten().InnerExceptions)
    {
        if (e is MyCustomException)
        {
            // Recover from the exception. Here we just
            // print the message for demonstration purposes.
            Console.WriteLine(e.Message);
        }
        else
        {
            throw;
        }
    }
    // or ...
   // ae.Flatten().Handle((ex) => ex is MyCustomException);

}

Ausnahmen bei getrennten untergeordneten Aufgaben

Standardmäßig werden untergeordnete Aufgaben als getrennte Aufgaben erstellt. Von getrennten Aufgaben ausgelöste Ausnahmen müssen in der unmittelbar übergeordneten Aufgabe behandelt oder erneut ausgelöst werden. Sie werden nicht auf die gleiche Weise wie angefügte untergeordnete Aufgaben an den aufrufenden Thread zurück geleitet. Das übergeordnete Element der höchsten Ebene kann eine Ausnahme von einem getrennten untergeordneten Element manuell erneut auslösen, sodass diese in eine AggregateException eingeschlossen und an den Verbindungsthread zurück geleitet wird.

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
var task1 = Task.Factory.StartNew(() =>
{

    var nested1 = Task.Factory.StartNew(() =>
    {
        throw new MyCustomException("Nested task faulted.");
    });

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

});

try
{
    task1.Wait();
}
catch (AggregateException ae)
{

    foreach (var e in ae.Flatten().InnerExceptions)
    {
        if (e is MyCustomException)
        {
            // Recover from the exception. Here we just
            // print the message for demonstration purposes.
            Console.WriteLine(e.Message);
        }
    }
}

Selbst wenn Sie mithilfe einer Fortsetzung eine Ausnahme in einer untergeordneten Aufgabe beachten, muss die Ausnahme dennoch von der übergeordneten Aufgabe beachtet werden.

Ausnahmen mit einem kooperativen Abbruch

Wenn Benutzercode in einer Aufgabe auf eine Abbruchanforderung reagiert, besteht das korrekte Verfahren darin, dass eine OperationCanceledException ausgelöst wird, die in dem Abbruchtoken, in dem die Anforderung übermittelt wurde, übergeben wird. Bevor die Ausnahme weitergeleitet wird, vergleicht die Aufgabeninstanz das Token in der Ausnahme mit dem Token, das beim Erstellen an sie übergeben wurde. Stimmen beide Token überein, gibt die Aufgabe eine TaskCanceledException weiter, die in die AggregateException eingeschlossen ist. Diese ist zu sehen, wenn die inneren Ausnahmen analysiert werden. Wenn der Verbindungsthread jedoch nicht auf die Aufgabe wartet, wird diese Ausnahme nicht weitergegeben. Weitere Informationen finden Sie unter Aufgabenabbruch.

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)
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

var task1 = Task.Factory.StartNew(() =>
{
    CancellationToken ct = token;
    while (someCondition)
    {
        // Do some work...
        Thread.SpinWait(50000);
        ct.ThrowIfCancellationRequested();
    }
},
token);

// No waiting required.

Filtern von inneren Ausnahmen mithilfe der Handle-Methode

Mit der Handle()-Methode können Sie Ausnahmen filtern, die ohne weitere Logik als "behandelt" definiert werden können. Im Benutzerdelegaten, der an Handle() weitergegeben wird, können Sie den Ausnahmetyp, dessen Message()-Eigenschaft oder beliebige andere Informationen analysieren und ermitteln, ob die Ausnahme keine Auswirkungen zeigt. Alle Ausnahmen, für die der Delegat false zurückgibt, werden in einer AggregateException-Instanz erneut ausgelöst, sobald Handle() wieder angezeigt wird.

Im folgenden Codeausschnitt wird eine foreach-Schleife über die inneren Ausnahmen verwendet.

For Each ex In ae.InnerExceptions
    If TypeOf (ex) Is MyCustomException Then
        Console.WriteLine(ex.Message)
    Else
        Throw
    End If
Next
foreach (var e in ae.InnerExceptions)
{
    if (e is MyCustomException)
    {
        Console.WriteLine(e.Message);
    }
    else
    {
        throw;
    }
}

Im folgenden Codeausschnitt wird die funktional entsprechende Verwendung der Handle()-Methode gezeigt.

ae.Handle(Function(ex)
              Return TypeOf (ex) Is MyCustomException
          End Function)
ae.Handle((ex) =>
{
    return ex is MyCustomException;
});

Beachten von Ausnahmen mit der Task.Exception-Eigenschaft

Wenn eine Aufgabe mit einem Faulted-Zustand abgeschlossen wird, kann die zugehörige Exception-Eigenschaft analysiert werden, um die spezifische Ausnahme zu ermitteln, die den Fehler verursacht hat. Eine gute Möglichkeit, die Exception-Eigenschaft zu beachten, ist eine Fortsetzung, die nur bei einem Fehler in der vorausgehenden Aufgabe ausgeführt wird, wie im folgenden Beispiel gezeigt.

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)
var task1 = Task.Factory.StartNew(() =>
{
    throw new MyCustomException("Task1 faulted.");
})
.ContinueWith((t) =>
    {
        Console.WriteLine("I have observed a {0}",
            t.Exception.InnerException.GetType().Name);
    },
    TaskContinuationOptions.OnlyOnFaulted);

In einer realen Anwendung würde der Fortsetzungsdelegat ausführliche Informationen zu der Ausnahme protokollieren und möglicherweise neue Aufgaben erzeugen, um die Ausnahme zu beheben.

UnobservedTaskException-Ereignis

In einigen Szenarien können häufig Ausnahmen ohne Auswirkungen auftreten (z. B. wenn Sie nicht vertrauenswürdige Plug-Ins hosten), und es kann schwierig sein, alle Ausnahmen manuell zu überwachen. In diesen Fällen können Sie das TaskScheduler.UnobservedTaskException-Ereignis behandeln. Mit der an den Handler übergebenen System.Threading.Tasks.UnobservedTaskExceptionEventArgs-Instanz kann verhindert werden, dass die nicht überwachte Ausnahme wieder an den Verbindungsthread übergeben wird.

Siehe auch

Konzepte

Task Parallel Library