Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Eine untergeordnete Aufgabe (oder geschachtelte Aufgabe) ist eine System.Threading.Tasks.Task-Instanz, die im Benutzerdelegaten einer anderen Aufgabe erstellt wird, die als übergeordnete Aufgabe bezeichnet wird. Eine untergeordnete Aufgabe kann entweder getrennt oder angefügt werden. Eine getrennte untergeordnete Aufgabe ist eine Aufgabe, die unabhängig von der übergeordneten ausgeführt wird. Eine angefügte ungeordnete Aufgabe ist eine geschachtelte Aufgabe, die mit der Option TaskCreationOptions.AttachedToParent erstellt wird, deren übergeordnete Aufgabe nicht explizit oder standardmäßig verhindert, dass die Aufgabe angefügt wird. Eine Aufgabe kann beliebig viele angefügte oder getrennte untergeordnete Aufgaben erstellen. Die Anzahl wird lediglich durch die Systemressourcen beschränkt.
In der folgenden Tabelle sind die grundlegenden Unterschiede zwischen den beiden Arten von Kindaufgaben aufgeführt.
Kategorie | Getrennte untergeordnete Aufgaben | Angefügte untergeordnete Aufgaben |
---|---|---|
Das übergeordnete Element wartet auf den Abschluss der untergeordneten Aufgaben. | Nein | Ja |
Das übergeordnete Element gibt von untergeordneten Aufgaben ausgelöste Ausnahmen weiter. | Nein | Ja |
Der Status des Elternteils hängt vom Status des Kindes ab. | Nein | Ja |
In den meisten Szenarios empfiehlt sich jedoch die Verwendung von getrennten untergeordneten Aufgabe, da die Beziehungen zu anderen Aufgaben weniger komplex sind. Aus diesem Grund werden in übergeordneten Aufgaben erstellte Aufgaben standardmäßig getrennt. Um die angefügte untergeordnete Aufgabe zu erstellen, müssen Sie die TaskCreationOptions.AttachedToParent-Option explizit angeben.
Getrennte untergeordnete Aufgaben
Obwohl eine untergeordnete Aufgabe durch eine übergeordnete Aufgabe erstellt wird, hängt diese standardmäßig von der übergeordneten Aufgabe ab. Im folgenden Beispiel erstellt eine übergeordnete Aufgabe eine einfache untergeordnete Aufgabe: Wenn Sie den Beispielcode mehrmals ausführen, stellen Sie möglicherweise fest, dass sich die Ausgabe des Beispiels von dem angezeigten unterscheidet und dass sich die Ausgabe bei jeder Ausführung des Codes ändern kann. Dies liegt daran, dass die übergeordnete und die untergeordnete Aufgabe unabhängig voneinander ausgeführt werden; das untergeordnete Element ist eine getrennte Aufgabe. Im Beispiel wird nur auf den Abschluss der übergeordneten Aufgabe gewartet, und die untergeordnete Aufgabe wird möglicherweise nicht ausgeführt oder abgeschlossen, ehe die Konsolen-App beendet wird.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example4
{
public static void Main()
{
Task parent = Task.Factory.StartNew(() =>
{
Console.WriteLine("Outer task executing.");
Task child = Task.Factory.StartNew(() =>
{
Console.WriteLine("Nested task starting.");
Thread.SpinWait(500000);
Console.WriteLine("Nested task completing.");
});
});
parent.Wait();
Console.WriteLine("Outer has completed.");
}
}
// The example produces output like the following:
// Outer task executing.
// Nested task starting.
// Outer has completed.
// Nested task completing.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Outer task executing.")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Nested task starting.")
Thread.SpinWait(500000)
Console.WriteLine("Nested task completing.")
End Sub)
End Sub)
parent.Wait()
Console.WriteLine("Outer task has completed.")
End Sub
End Module
' The example produces output like the following:
' Outer task executing.
' Nested task starting.
' Outer task has completed.
' Nested task completing.
Wenn die untergeordnete Aufgabe durch ein Task<TResult>-Objekt statt einem Task-Objekt dargestellt wird, können Sie sicherstellen, dass die übergeordnete Aufgabe auf das Abschließen der Aufgabe des untergeordneten Elements wartet, indem Sie auf die Task<TResult>.Result-Eigenschaft des untergeordneten Elements zugreift, auch wenn es sich dabei um eine getrennte untergeordnete Aufgabe handelt. Die Result Eigenschaft blockiert, bis die Aufgabe abgeschlossen ist, wie im folgenden Beispiel gezeigt.
using System;
using System.Threading;
using System.Threading.Tasks;
class Example3
{
static void Main()
{
var outer = Task<int>.Factory.StartNew(() =>
{
Console.WriteLine("Outer task executing.");
var nested = Task<int>.Factory.StartNew(() =>
{
Console.WriteLine("Nested task starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Nested task completing.");
return 42;
});
// Parent will wait for this detached child.
return nested.Result;
});
Console.WriteLine($"Outer has returned {outer.Result}.");
}
}
// The example displays the following output:
// Outer task executing.
// Nested task starting.
// Nested task completing.
// Outer has returned 42.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Outer task executing.")
Dim child = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Nested task starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Nested task completing.")
Return 42
End Function)
Return child.Result
End Function)
Console.WriteLine("Outer has returned {0}", parent.Result)
End Sub
End Module
' The example displays the following output:
' Outer task executing.
' Nested task starting.
' Detached task completing.
' Outer has returned 42
Angefügte untergeordnete Aufgaben
Anders als getrennte untergeordnete Aufgaben werden angefügte untergeordnete Aufgaben eng mit dem übergeordneten Element synchronisiert. Sie können die getrennte untergeordnete Aufgabe im vorherigen Beispiel in eine angefügte untergeordnete Aufgabe ändern, indem Sie wie im folgenden Beispiel gezeigt die TaskCreationOptions.AttachedToParent-Option in der Aufgabenerstellungsanweisung verwenden. In diesem Code schließt die angefügte untergeordnete Aufgabe vor ihrem übergeordneten Element ab. Daher ist die Ausgabe aus dem Beispiel jedes Mal identisch, wenn Sie den Code ausführen.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays the following output:
// Parent task executing.
// Attached child starting.
// Attached child completing.
// Parent has completed.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Parent task executing")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completing.")
End Sub, TaskCreationOptions.AttachedToParent)
End Sub)
parent.Wait()
Console.WriteLine("Parent has completed.")
End Sub
End Module
' The example displays the following output:
' Parent task executing.
' Attached child starting.
' Attached child completing.
' Parent has completed.
Mithilfe von angefügten untergeordneten Aufgaben können Sie synchronisierte Diagramme von asynchronen Vorgängen erstellen.
Die untergeordnete Aufgabe kann jedoch nur dann an die übergeordnete Aufgabe angefügt werden, wenn die übergeordnete Aufgabe das Anfügen von untergeordneten Aufgaben nicht verhindert. Übergeordnete Aufgaben können explizit verhindern, dass untergeordnete Aufgaben an sie angefügt werden, indem sie die TaskCreationOptions.DenyChildAttach Option im Klassenkonstruktor der übergeordneten Aufgabe oder der TaskFactory.StartNew Methode angeben. Elternaufgaben verhindern implizit, dass untergeordnete Aufgaben sich mit ihnen verbinden, wenn diese durch das Aufrufen der Task.Run-Methode erstellt werden. Im folgenden Beispiel wird dies veranschaulicht. Es ist identisch mit dem vorherigen Beispiel, mit der Ausnahme, dass die übergeordnete Aufgabe durch Aufrufen der Task.Run(Action) Methode und nicht durch die TaskFactory.StartNew(Action) Methode erstellt wird. Da die untergeordnete Aufgabe nicht an die übergeordnete Aufgabe angefügt werden kann, ist die Ausgabe dieses Beispiels nicht vorhersehbar. Da die standardmäßigen Aufgabenerstellungsoptionen für Task.Run-Überladungen TaskCreationOptions.DenyChildAttach einschließen, entspricht dieses Beispiel funktional dem ersten Beispiel im Abschnitt „Getrennte untergeordnete Aufgaben“.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example2
{
public static void Main()
{
var parent = Task.Run(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays output like the following:
// Parent task executing.
// Parent has completed.
// Attached child starting.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task.Run(Sub()
Console.WriteLine("Parent task executing.")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completing.")
End Sub, TaskCreationOptions.AttachedToParent)
End Sub)
parent.Wait()
Console.WriteLine("Parent has completed.")
End Sub
End Module
' The example displays output like the following:
' Parent task executing.
' Parent has completed.
' Attached child starting.
Ausnahmen in untergeordneten Aufgaben
Wenn eine getrennte untergeordnete Aufgabe eine Ausnahme auslöst, muss diese direkt in der übergeordneten Aufgabe beachtet oder behandelt werden, ebenso wie dies bei nicht geschachtelten Aufgaben der Fall ist. Wenn eine angefügte untergeordnete Aufgabe eine Ausnahme auslöst, wird die Ausnahme automatisch an die übergeordnete Aufgabe weitergeleitet und an den Thread zurückgesendet, der wartet oder auf die Task<TResult>.Result-Eigenschaft der Aufgabe zugreifen möchte. Daher können durch Verwendung angefügter untergeordneter Aufgaben alle Ausnahmen an nur einem Punkt, beim Aufruf von Task.Wait im aufrufenden Thread, verarbeitet werden. Weitere Informationen finden Sie unter "Ausnahmebehandlung".
Abbruch und untergeordnete Aufgaben
Das Abbrechen einer Aufgabe ist kooperativ. Um "abbrechbar" zu sein, muss jede angefügte oder getrennte untergeordnete Aufgabe den Status des Abbruchtokens überwachen. Wenn Sie mit einer Abbruchanforderung ein übergeordnetes Element und alle untergeordneten Elemente abbrechen möchten, übergeben Sie das gleiche Token als Argument an alle Aufgaben und stellen in jeder Aufgabe die Logik zum Reagieren auf die Anforderung in bereit. Weitere Informationen finden Sie unter Aufgabenabbruch und Vorgehensweise: Abbrechen einer Aufgabe und ihrer untergeordneten Elemente.
Wenn das Elternteil absagt
Wenn eine übergeordnete Aufgabe abgebrochen wird, bevor eine untergeordnete Aufgabe gestartet wurde, wird die untergeordnete Aufgabe nie gestartet. Wenn ein übergeordnetes Element nach dem Start einer untergeordneten Aufgabe abgebrochen wird, wird die untergeordnete bis zum Abschluss ausgeführt, sofern keine eigene Abbruchlogik vorhanden ist. Weitere Informationen finden Sie unter "Abbruch der Aufgabe".
Abbruch einer getrennten untergeordneten Aufgabe
Wenn eine getrennte untergeordnete Aufgabe mit dem gleichen Token abgebrochen wird, das an die übergeordnete Aufgabe übergeben wurde, und das übergeordnete Element nicht auf die untergeordnete Aufgabe wartet, wird keine Ausnahme weitergeleitet, da die Ausnahme als Kooperationsabbruch ohne Auswirkungen behandelt wird. Dieses Verhalten entspricht dem eines beliebigen Vorgangs auf oberster Ebene.
Abbruch einer angefügten untergeordneten Aufgabe
Wenn eine angefügte untergeordnete Aufgabe mit dem gleichen Token abgebrochen wird, das an die übergeordnete Aufgabe übergeben wurde, wird eine TaskCanceledException an den Verbindungsthread in einer AggregateException weitergeleitet. Sie müssen auf die übergeordnete Aufgabe warten, damit Sie die Ausnahmen ohne Auswirkung zusammen mit allen einen Fehler auslösenden Ausnahmen behandeln können, die durch ein Diagramm angefügter untergeordneter Aufgaben nach oben weitergeleitet werden.
Weitere Informationen finden Sie unter "Ausnahmebehandlung".
Verhindern, dass sich eine untergeordnete Aufgabe mit ihrem übergeordneten Element verbindet
Ein Ausnahmefehler, der durch eine untergeordnete Aufgabe ausgelöst wird, wird an die übergeordnete Aufgabe weitergegeben. Sie können dieses Verhalten nutzen, um alle Ausnahmen von untergeordneten Aufgaben einer Stammaufgabe zu beobachten anstatt eine Aufgabenstruktur zu durchlaufen. Die Ausbreitung von Ausnahmen kann jedoch problematisch sein, wenn eine übergeordnete Aufgabe nicht erwartet, dass anderer Code angefügt wird. Betrachten Sie beispielsweise eine App, die eine Bibliothekskomponente eines Drittanbieters aus einem Task Objekt aufruft. Wenn die Bibliothekskomponente von Drittanbietern auch ein Task-Objekt erstellt und TaskCreationOptions.AttachedToParent angibt, um es zur übergeordneten Aufgabe anzufügen, werden alle Ausnahmefehler, die in der untergeordneten Aufgabe auftreten, an das übergeordnete Element weitergegeben. Dies kann zu unerwartetem Verhalten in der Haupt-App führen.
Um zu verhindern, dass eine untergeordnete Aufgabe an die übergeordnete Aufgabe angefügt wird, geben Sie die TaskCreationOptions.DenyChildAttach Option an, wenn Sie das übergeordnete Task Objekt oder Task<TResult> Objekt erstellen. Wenn eine Aufgabe versucht, sich an ihre übergeordnete Aufgabe anzufügen und für die übergeordnete Aufgabe die TaskCreationOptions.DenyChildAttach-Option festgelegt wurde, kann die untergeordnete Aufgabe nicht an die übergeordnete Aufgabe angefügt werden und wird so ausgeführt, als sei die TaskCreationOptions.AttachedToParent-Option nicht festgelegt worden.
Sie können auch verhindern, dass sich eine untergeordnete Aufgabe an das übergeordnete Element anfügt, wenn die untergeordnete Aufgabe nicht rechtzeitig beendet wird. Da eine übergeordnete Aufgabe nicht beendet wird, ehe alle untergeordneten Aufgaben abgeschlossen sind, kann eine untergeordnete Aufgabe mit langer Laufzeit zu einer Verschlechterung der Leistung der gesamten App führen. Ein Beispiel zur Verbesserung der App-Leistung durch Verhindern des Anfügens einer Aufgabe an das übergeordnete Element finden Sie unter Gewusst wie: Verhindern des Anfügens einer untergeordneten Aufgabe an die übergeordnete Aufgabe.