Aufgabenbasiertes asynchrones Programmieren

Die Task Parallel Library (TPL) basiert auf dem Konzept einer Aufgabe, die einen asynchronen Vorgang darstellt. In einigen Dingen ist eine Aufgabe vergleichbar mit einer ThreadPool, weist jedoch eine höhere Abstraktionsebene auf. Der Begriff Aufgabenparallelität bezeichnet eine oder mehrere eigenständige Aufgaben, die gleichzeitig ausgeführt werden. Aufgaben bieten zwei Hauptvorteile:

  • Effiziente und skalierbare Verwendung von Systemressourcen.

    Hinter den Kulissen werden Aufgaben in die ThreadPoolWarteschlange gestellt, die mit Algorithmen erweitert wurden, die die Anzahl der Threads bestimmen und anpassen und den Lastenausgleich bereitstellen, um den Durchsatz zu maximieren. Aufgaben sind hierdurch relativ einfach strukturiert, und Sie können für eine differenzierte Parallelität viele Aufgaben erstellen.

  • Stärker programmgesteuerte Kontrolle als bei Threads oder Arbeitselementen.

    Aufgaben und das diese umgebende Framework stellen einen umfangreichen Satz von APIs bereit, die Warten, Abbruch, Fortsetzungen, robuste Ausnahmebehandlung, detaillierte Zustandsangaben, benutzerdefinierte Planung und Vieles mehr unterstützen.

Aus diesen Gründen ist die TPL die bevorzugte API zum Schreiben von asynchronem Code, parallelem Code und Multithreadcode in .NET.

Implizites Erstellen und Ausführen von Aufgaben

Die Parallel.Invoke-Methode bietet eine einfache Möglichkeit, eine beliebige Anzahl willkürlicher Anweisungen gleichzeitig auszuführen. Sie müssen nur einen Action-Delegaten für jede Arbeitsaufgabe übergeben. Die einfachste Möglichkeit, diese Delegaten zu erstellen, sind Lambdaausdrücke. Der Lambdaausdruck kann entweder eine benannte Methode aufrufen oder den Code inline bereitstellen. Im folgenden Beispiel wird ein grundlegender Invoke-Aufruf dargestellt, der zwei Aufgaben erstellt und startet, die gleichzeitig ausgeführt werden. Die erste Aufgabe wird durch einen Lambda-Ausdruck dargestellt, der die DoSomeWork-Methode aufruft, und die zweite Aufgabe wird durch einen Lambda-Ausdruck dargestellt, der die DoSomeOtherWork-Methode aufruft.

Hinweis

Diese Dokumentation definiert Delegaten in TPL mithilfe von Lambdaausdrücken. Falls Sie mit der Verwendung von Lambda-Ausdrücken in C# oder Visual Basic nicht vertraut sind, finden Sie entsprechende Informationen unter Lambda Expressions in PLINQ and TPL (Lambda-Ausdrücke in PLINQ und TPL).

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())

Hinweis

Die Anzahl von Task-Instanzen, die im Hintergrund von Invoke erstellt werden, ist nicht notwendigerweise mit der Anzahl der bereitgestellten Delegaten identisch. Die TPL kann verschiedene Optimierungen einsetzen, besonders bei einer großen Anzahl von Delegaten.

Weitere Informationen finden Sie unter Vorgehensweise: Ausführen von parallelen Vorgängen mithilfe von „Parallel.Invoke“.

Wenn Sie die Aufgabenausführung präziser steuern oder einen Wert aus der Aufgabe zurückgeben möchten, müssen Sie expliziter mit Task-Objekten arbeiten.

Explizites Erstellen und Ausführen von Aufgaben

Eine Aufgabe, die keinen Wert zurückgibt, wird durch die System.Threading.Tasks.Task-Klasse dargestellt. Eine Aufgabe, die einen Wert zurückgibt, wird durch die System.Threading.Tasks.Task<TResult>-Klasse dargestellt, die von Task erbt. Das Aufgabenobjekt verarbeitet die Infrastrukturdetails und stellt Methoden sowie Eigenschaften bereit, auf die während der gesamten Lebensdauer der Aufgabe vom aufrufenden Thread aus zugegriffen werden kann. Sie können beispielsweise jederzeit auf die Status-Eigenschaft einer Aufgabe zugreifen, um zu ermitteln, ob die Ausführung gestartet, abgeschlossen oder abgebrochen wurde bzw. ob eine Ausnahme ausgelöst wurde. Der Status wird durch eine TaskStatus-Enumeration dargestellt.

Wenn Sie eine Aufgabe erstellen, weisen Sie dieser einen Benutzerdelegaten zu, der den von der Aufgabe ausgeführten Code kapselt. Der Delegat kann als benannter Delegat, als anonyme Methode oder als Lambda-Ausdruck angegeben werden. Lambdaausdrücke können einen Aufruf einer benannten Methode enthalten, wie im folgenden Beispiel gezeigt. Beachten Sie, dass im Beispiel ein Aufruf der Task.Wait-Methode enthalten ist, um sicherzustellen, dass die Aufgabe abgeschlossen ist, ehe die Konsolenmodusanwendung beendet wird.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Create a task and supply a user delegate by using a lambda expression.
      Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
      // Start the task.
      taskA.Start();

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                        Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output like the following:
//       Hello from thread 'Main'.
//       Hello from taskA.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Thread.CurrentThread.Name = "Main"

        ' Create a task and supply a user delegate by using a lambda expression. 
        Dim taskA = New Task(Sub() Console.WriteLine("Hello from taskA."))
        ' Start the task.
        taskA.Start()

        ' Output a message from the calling thread.
        Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name)
        taskA.Wait()
    End Sub
End Module
' The example displays output like the following:
'    Hello from thread 'Main'.
'    Hello from taskA.

Sie können auch die Task.Run-Methoden verwenden, um eine Aufgabe in einem Vorgang zu erstellen und zu starten. Um den Vorgang zu verwalten, verwenden die Run Methoden den Standardaufgabenplaner, unabhängig davon, welcher Vorgangszeitplaner dem aktuellen Thread zugeordnet ist. Wenn eine präzisere Steuerung der Erstellung und Planung von Aufgaben nicht erforderlich ist, sollten diese vorzugsweise mit den Run-Methoden erstellt und gestartet werden.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Define and run the task.
      Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output like the following:
//       Hello from thread 'Main'.
//       Hello from taskA.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Thread.CurrentThread.Name = "Main"

        Dim taskA As Task = Task.Run(Sub() Console.WriteLine("Hello from taskA."))

        ' Output a message from the calling thread.
        Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name)
        taskA.Wait()
    End Sub
End Module
' The example displays output like the following:
'    Hello from thread 'Main'.
'    Hello from taskA.

Sie können auch die TaskFactory.StartNew-Methode verwenden, um eine Aufgabe in einem Schritt zu erstellen und zu starten. Verwenden Sie diese Methode, wenn Erstellung und Planung nicht getrennt werden müssen, zusätzliche Aufgabenerstellungsoptionen oder die Nutzung eines bestimmten Planers erforderlich sind oder der Aufgabe zusätzliche Zustände übergeben werden müssen, die über deren Task.AsyncState-Eigenschaft abgerufen werden können, wie im folgenden Beispiel dargestellt.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
      foreach (var task in taskArray) {
         var data = task.AsyncState as CustomData;
         if (data != null)
            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                              data.Name, data.CreationTime, data.ThreadNum);
      }
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As CustomData = TryCast(obj, CustomData)
                                                     If data Is Nothing Then Return

                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                 End Sub,
                                                 New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks})
        Next
        Task.WaitAll(taskArray)

        For Each task In taskArray
            Dim data = TryCast(task.AsyncState, CustomData)
            If data IsNot Nothing Then
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum)
            End If
        Next
    End Sub
End Module
' The example displays output like the following:
'    Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
'    Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
'    Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

Task und Task<TResult> machen jeweils eine statische Factory-Eigenschaft verfügbar, von der eine Standardinstanz von TaskFactory zurückgegeben wird, sodass Sie die Methode als Task.Factory.StartNew() aufrufen können. Darüber hinaus verfügen die Aufgaben im Beispiel, da sie vom System.Threading.Tasks.Task<TResult>-Typ sind, jeweils über eine öffentliche Task<TResult>.Result-Eigenschaft, die das Ergebnis der Berechnung enthält. Die Aufgaben werden asynchron ausgeführt und können in einer beliebigen Reihenfolge abgeschlossen werden. Wenn auf die Result-Eigenschaft zugegriffen wird, ehe die Berechnung beendet wurde, sperrt die Eigenschaft den aufrufenden Thread, bis der Wert verfügbar ist.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
        Task<Double>[] taskArray = { Task<Double>.Factory.StartNew(() => DoComputation(1.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(100.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(1000.0)) };

        var results = new Double[taskArray.Length];
        Double sum = 0;

        for (int i = 0; i < taskArray.Length; i++) {
            results[i] = taskArray[i].Result;
            Console.Write("{0:N1} {1}", results[i],
                              i == taskArray.Length - 1 ? "= " : "+ ");
            sum += results[i];
        }
        Console.WriteLine("{0:N1}", sum);
   }

   private static Double DoComputation(Double start)
   {
      Double sum = 0;
      for (var value = start; value <= start + 10; value += .1)
         sum += value;

      return sum;
   }
}
// The example displays the following output:
//        606.0 + 10,605.0 + 100,495.0 = 111,706.0
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation(1.0)),
                            Task(Of Double).Factory.StartNew(Function() DoComputation(100.0)),
                            Task(Of Double).Factory.StartNew(Function() DoComputation(1000.0))}

        Dim results(taskArray.Length - 1) As Double
        Dim sum As Double

        For i As Integer = 0 To taskArray.Length - 1
            results(i) = taskArray(i).Result
            Console.Write("{0:N1} {1}", results(i),
                              If(i = taskArray.Length - 1, "= ", "+ "))
            sum += results(i)
        Next
        Console.WriteLine("{0:N1}", sum)
    End Sub

    Private Function DoComputation(start As Double) As Double
        Dim sum As Double
        For value As Double = start To start + 10 Step .1
            sum += value
        Next
        Return sum
    End Function
End Module
' The example displays the following output:
'       606.0 + 10,605.0 + 100,495.0 = 111,706.0

Weitere Informationen finden Sie unter Vorgehensweise: Zurückgeben eines Werts aus einer Aufgabe.

Wenn Sie einen Lambdaausdruck verwenden, um einen Delegaten zu erstellen, haben Sie Zugriff auf alle Variablen, die an dieser Stelle im Quellcode sichtbar sind. In einigen Fällen jedoch, insbesondere in Schleifen, wird die Variable nicht wie erwartet vom Lambda-Ausdruck erfasst. Er erfasst nur den Bezug der Variablen, nicht den Wert, da er nach jeder Iteration stummgeschaltet wird. Das Problem wird anhand des folgenden Beispiels veranschaulicht. Es wird ein Schleifenzähler an den Lambda-Ausdruck übergeben, wodurch ein CustomData-Objekt instanziiert wird und der Schleifenzähler als Objektbezeichner verwendet. Die Ausgabe im Beispiel zeigt, dass jedes CustomData-Objekt einen identischen Bezeichner hat.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in the loop
      // counter. This produces an unexpected result.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj) => {
                                                 var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                 Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                              i );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #10 created at 635116418427727841 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427727841 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427737842 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        ' Create the task object by using an Action(Of Object) to pass in the loop
        ' counter. This produces an unexpected result.
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks}
                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                     Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                       data.Name, data.CreationTime, data.ThreadNum)
                                                 End Sub,
                                                 i)
        Next
        Task.WaitAll(taskArray)
    End Sub
End Module
' The example displays output like the following:
'       Task #10 created at 635116418427727841 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427727841 on thread #3.
'       Task #10 created at 635116418427747843 on thread #3.
'       Task #10 created at 635116418427747843 on thread #3.
'       Task #10 created at 635116418427737842 on thread #4.

Sie können auf den Wert jeder Iteration zugreifen, indem Sie für die Aufgabe über ihren Konstruktor ein Zustandsobjekt bereitstellen. Im folgenden Beispiel wird das vorhergehende Beispiel geändert, indem der Schleifenzähler beim Erstellen des CustomData-Objekts verwendet wird, das wiederum an den Lambda-Ausdruck übergeben wird. Wie die Ausgabe im Beispiel zeigt, weist jedes CustomData-Objekt jetzt einen eindeutigen Bezeichner basierend auf den Wert des Schleifenzählers zum Zeitpunkt der Instanziierung des Objekts auf.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in custom data
      // to the Task constructor. This is useful when you need to capture outer variables
      // from within a loop.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                  Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        ' Create the task object by using an Action(Of Object) to pass in custom data
        ' to the Task constructor. This is useful when you need to capture outer variables
        ' from within a loop. 
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As CustomData = TryCast(obj, CustomData)
                                                     If data Is Nothing Then Return

                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                     Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                       data.Name, data.CreationTime, data.ThreadNum)
                                                 End Sub,
                                                 New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks})
        Next
        Task.WaitAll(taskArray)
    End Sub
End Module
' The example displays output like the following:
'       Task #0 created at 635116412924597583 on thread #3.
'       Task #1 created at 635116412924607584 on thread #4.
'       Task #3 created at 635116412924607584 on thread #4.
'       Task #4 created at 635116412924607584 on thread #4.
'       Task #2 created at 635116412924607584 on thread #3.
'       Task #6 created at 635116412924607584 on thread #3.
'       Task #5 created at 635116412924607584 on thread #4.
'       Task #8 created at 635116412924607584 on thread #4.
'       Task #7 created at 635116412924607584 on thread #3.
'       Task #9 created at 635116412924607584 on thread #4.

Dieser Zustand wird als Argument an den Aufgabendelegaten übergeben, und mit der Task.AsyncState-Eigenschaft kann über das Aufgabenobjekt auf den Zustand zugegriffen werden. Das folgende Beispiel zeigt eine Abwandlung des vorherigen Beispiels. Es verwendet die AsyncState-Eigenschaft, um Informationen zu den CustomData-Objekten anzuzeigen, die an den Lambdaausdruck übergeben wurden.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
      foreach (var task in taskArray) {
         var data = task.AsyncState as CustomData;
         if (data != null)
            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                              data.Name, data.CreationTime, data.ThreadNum);
      }
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As CustomData = TryCast(obj, CustomData)
                                                     If data Is Nothing Then Return

                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                 End Sub,
                                                 New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks})
        Next
        Task.WaitAll(taskArray)

        For Each task In taskArray
            Dim data = TryCast(task.AsyncState, CustomData)
            If data IsNot Nothing Then
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum)
            End If
        Next
    End Sub
End Module
' The example displays output like the following:
'    Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
'    Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
'    Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

Aufgaben-ID

Jeder Aufgabe wird eine ganzzahlige ID zugeordnet, durch die diese in einer Anwendungsdomäne eindeutig identifiziert wird und auf die mit der Task.Id-Eigenschaft zugegriffen werden kann. Die ID vereinfacht das Anzeigen von Aufgabeninformationen in den Fenstern Parallele Stapel und Parallele Aufgaben des Visual Studio-Debuggers. Die ID wird verzögert erzeugt, d. h., sie wird erst nach einer entsprechenden Anforderung erstellt. Eine Aufgabe kann daher bei jeder Programmausführung eine andere ID aufweisen. Weitere Informationen zum Anzeigen von Aufgaben-IDs im Debugger finden Sie unter Verwenden des Fensters „Aufgaben“ und Verwenden des Fensters „Parallele Stapel“.

Aufgabenerstellungsoptionen

Die meisten APIs, die Aufgaben erstellen, stellen Überladungen bereit, die einen TaskCreationOptions-Parameter akzeptieren. Durch Angabe mindestens einer dieser Optionen teilen Sie dem Taskplaner mit, wie die Task im Threadpool geplant werden soll. Die Optionen können mit einem bitweisen OR-Vorgang kombiniert werden.

Im folgenden Beispiel wird eine Task veranschaulicht, die über die LongRunning-Option und die PreferFairness-Option verfügt.

var task3 = new Task(() => MyLongRunningMethod(),
                    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Dim task3 = New Task(Sub() MyLongRunningMethod(),
                        TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()

Aufgaben, Threads und Kultur

Jeder Thread weist eine zugeordnete Kultur und Benutzeroberflächenkultur auf, die entsprechend durch die Thread.CurrentCulture- bzw. die Thread.CurrentUICulture-Eigenschaft definiert sind. Die Kultur eines Threads wird in Vorgängen wie Formatieren, Analysieren, Sortieren und Zeichenfolgenvergleich verwendet. Die Benutzeroberflächenkultur eines Threads wird für Nachschlagen in Ressourcen verwendet.

Sofern Sie nicht mithilfe der CultureInfo.DefaultThreadCurrentCulture- und CultureInfo.DefaultThreadCurrentUICulture-Eigenschaften eine Standardkultur für alle Threads in einer Anwendungsdomäne festlegen, werden die Standardkultur und -benutzeroberflächenkultur eines Threads durch die Systemkultur definiert. Wenn Sie die Kultur eines Threads explizit festlegen und einen neuen Thread starten, erbt der neue Thread nicht die Kultur des aufrufenden Threads, sondern die Standardkultur des Systems wird als seine Kultur verwendet. Bei der taskbasierten Programmierung verwenden Tasks jedoch die Kultur des aufrufenden Threads, selbst wenn der Task in einem anderen Thread asynchron ausgeführt wird.

Das folgende Beispiel bietet eine einfache Veranschaulichung. Die aktuelle App-Kultur wird in „Französisch (Frankreich)“ geändert (oder, falls die aktuelle Kultur bereits „Französisch (Frankreich)“ ist, in „Englisch (USA)“). Anschließend wird der Delegat formatDelegate aufgerufen, der einige Zahlen zurückgibt, die als Währungswerte in der neuen Kultur formatiert sind. Eine Task verwendet die Kultur des aufrufenden Threads unabhängig davon, ob der Delegat von der Task synchron oder asynchron aufgerufen wird.

using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
       decimal[] values = { 163025412.32m, 18905365.59m };
       string formatString = "C2";
       Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",
                                                                           CultureInfo.CurrentCulture.Name,
                                                                           Thread.CurrentThread.ManagedThreadId);
                                             foreach (var value in values)
                                                output += String.Format("{0}   ", value.ToString(formatString));

                                             output += Environment.NewLine;
                                             return output;
                                           };

       Console.WriteLine("The example is running on thread {0}",
                         Thread.CurrentThread.ManagedThreadId);
       // Make the current culture different from the system culture.
       Console.WriteLine("The current culture is {0}",
                         CultureInfo.CurrentCulture.Name);
       if (CultureInfo.CurrentCulture.Name == "fr-FR")
          Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
       else
          Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

       Console.WriteLine("Changed the current culture to {0}.\n",
                         CultureInfo.CurrentCulture.Name);

       // Execute the delegate synchronously.
       Console.WriteLine("Executing the delegate synchronously:");
       Console.WriteLine(formatDelegate());

       // Call an async delegate to format the values using one format string.
       Console.WriteLine("Executing a task asynchronously:");
       var t1 = Task.Run(formatDelegate);
       Console.WriteLine(t1.Result);

       Console.WriteLine("Executing a task synchronously:");
       var t2 = new Task<String>(formatDelegate);
       t2.RunSynchronously();
       Console.WriteLine(t2.Result);
   }
}
// The example displays the following output:
//         The example is running on thread 1
//         The current culture is en-US
//         Changed the current culture to fr-FR.
//
//         Executing the delegate synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task asynchronously:
//         Formatting using the fr-FR culture on thread 3.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
Imports System.Globalization
Imports System.Threading

Module Example
    Public Sub Main()
        Dim values() As Decimal = {163025412.32D, 18905365.59D}
        Dim formatString As String = "C2"
        Dim formatDelegate As Func(Of String) = Function()
                                                    Dim output As String = String.Format("Formatting using the {0} culture on thread {1}.",
                                                                                         CultureInfo.CurrentCulture.Name,
                                                                                         Thread.CurrentThread.ManagedThreadId)
                                                    output += Environment.NewLine
                                                    For Each value In values
                                                        output += String.Format("{0}   ", value.ToString(formatString))
                                                    Next
                                                    output += Environment.NewLine
                                                    Return output
                                                End Function

        Console.WriteLine("The example is running on thread {0}",
                          Thread.CurrentThread.ManagedThreadId)
        ' Make the current culture different from the system culture.
        Console.WriteLine("The current culture is {0}",
                          CultureInfo.CurrentCulture.Name)
        If CultureInfo.CurrentCulture.Name = "fr-FR" Then
            Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Else
            Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")
        End If
        Console.WriteLine("Changed the current culture to {0}.",
                          CultureInfo.CurrentCulture.Name)
        Console.WriteLine()

        ' Execute the delegate synchronously.
        Console.WriteLine("Executing the delegate synchronously:")
        Console.WriteLine(formatDelegate())

        ' Call an async delegate to format the values using one format string.
        Console.WriteLine("Executing a task asynchronously:")
        Dim t1 = Task.Run(formatDelegate)
        Console.WriteLine(t1.Result)

        Console.WriteLine("Executing a task synchronously:")
        Dim t2 = New Task(Of String)(formatDelegate)
        t2.RunSynchronously()
        Console.WriteLine(t2.Result)
    End Sub
End Module

' The example displays the following output:
'
'          The example is running on thread 1
'          The current culture is en-US
'          Changed the current culture to fr-FR.
'
'          Executing the delegate synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task asynchronously:
'          Formatting Imports the fr-FR culture on thread 3.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €

Hinweis

In früheren Versionen des .NET Framework als Version 4.6 wurde die Kultur einer Task durch die Kultur des Threads bestimmt, in dem diese ausgeführt wurde, und nicht durch die Kultur des aufrufenden Threads. Für asynchrone Tasks bedeutet dies, dass sich die Kultur, die von der Task verwendet wird, von der Kultur des aufrufenden Threads unterscheiden könnte.

Weitere Informationen zu asynchronen Aufgaben und Kultur, finden Sie im Thema CultureInfo im Abschnitt "Kultur und asynchrone aufgabenbasierte Vorgänge".

Erstellen von Aufgabenfortsetzungen

Mithilfe der Methoden Task.ContinueWith und Task<TResult>.ContinueWith können Sie eine Aufgabe angeben, die gestartet werden soll, wenn die Vorgängeraufgabe abgeschlossen wurde. An den Delegaten der Fortsetzungsaufgabe wird ein Verweis auf die vorherige Aufgabe übergeben, damit dieser den Status der vorherigen Aufgabe überprüfen kann und durch Abruf des Werts der Task<TResult>.Result-Eigenschaft die Ausgabe der vorherigen Aufgabe als Eingabe für die Fortsetzung verwenden kann.

Im folgenden Beispiel wird die getData-Aufgabe durch einen Aufruf der TaskFactory.StartNew<TResult>(Func<TResult>)-Methode gestartet. Die processData-Aufgabe wird automatisch gestartet, wenn getData endet, und displayData wird gestartet, wenn processData endet. getData erzeugt ein Ganzzahlarray, auf das über die processData-Aufgabe durch die getData-Eigenschaft der Task<TResult>.Result-Aufgabe zugegriffen werden kann. Die processData Aufgabe verarbeitet das Array und gibt ein Ergebnis zurück, dessen Typ vom Rückgabetyp des Lambda-Ausdrucks abgeleitet wird, der an die Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>)-Methode übergeben wurde. Die displayData-Aufgabe wird automatisch ausgeführt, wenn processData endet und das Tuple<T1,T2,T3>-Objekt, das durch den processData-Lambda-Ausdruck zurückgegeben wurde, über die displayData-Eigenschaft der processData-Aufgabe für die Task<TResult>.Result-Aufgabe zugänglich ist. Die displayData-Aufgabe nutzt das Ergebnis der processData-Aufgabe und erzeugt ein Ergebnis, dessen Typ auf ähnliche Weise abgeleitet wird und das dem Programm in der Result-Eigenschaft zur Verfügung gestellt wird.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var getData = Task.Factory.StartNew(() => {
                                             Random rnd = new Random();
                                             int[] values = new int[100];
                                             for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                values[ctr] = rnd.Next();

                                             return values;
                                          } );
      var processData = getData.ContinueWith((x) => {
                                                int n = x.Result.Length;
                                                long sum = 0;
                                                double mean;

                                                for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                                   sum += x.Result[ctr];

                                                mean = sum / (double) n;
                                                return Tuple.Create(n, sum, mean);
                                             } );
      var displayData = processData.ContinueWith((x) => {
                                                    return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                         x.Result.Item1, x.Result.Item2,
                                                                         x.Result.Item3);
                                                 } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim getData = Task.Factory.StartNew(Function()
                                                Dim rnd As New Random()
                                                Dim values(99) As Integer
                                                For ctr = 0 To values.GetUpperBound(0)
                                                    values(ctr) = rnd.Next()
                                                Next
                                                Return values
                                            End Function)
        Dim processData = getData.ContinueWith(Function(x)
                                                   Dim n As Integer = x.Result.Length
                                                   Dim sum As Long
                                                   Dim mean As Double

                                                   For ctr = 0 To x.Result.GetUpperBound(0)
                                                       sum += x.Result(ctr)
                                                   Next
                                                   mean = sum / n
                                                   Return Tuple.Create(n, sum, mean)
                                               End Function)
        Dim displayData = processData.ContinueWith(Function(x)
                                                       Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                            x.Result.Item1, x.Result.Item2,
                                                                            x.Result.Item3)
                                                   End Function)
        Console.WriteLine(displayData.Result)
    End Sub
End Module
' The example displays output like the following:
'   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Da Task.ContinueWith eine Instanzmethode ist, können Sie die Methodenaufrufe verketten, anstatt ein Task<TResult>-Objekt für jede Vorgängeraufgabe zu instanziieren. Das folgende Beispiel entspricht funktionell dem vorherigen Beispiel, verkettet aber die Aufrufe der Task.ContinueWith-Methode. Beachten Sie, dass das Task<TResult>-Objekt, das durch die Kette von Methodenaufrufen zurückgegeben wird, die endgültige Fortsetzungsaufgabe ist.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var displayData = Task.Factory.StartNew(() => {
                                                 Random rnd = new Random();
                                                 int[] values = new int[100];
                                                 for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                    values[ctr] = rnd.Next();

                                                 return values;
                                              } ).
                        ContinueWith((x) => {
                                        int n = x.Result.Length;
                                        long sum = 0;
                                        double mean;

                                        for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                           sum += x.Result[ctr];

                                        mean = sum / (double) n;
                                        return Tuple.Create(n, sum, mean);
                                     } ).
                        ContinueWith((x) => {
                                        return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                             x.Result.Item1, x.Result.Item2,
                                                             x.Result.Item3);
                                     } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim displayData = Task.Factory.StartNew(Function()
                                                    Dim rnd As New Random()
                                                    Dim values(99) As Integer
                                                    For ctr = 0 To values.GetUpperBound(0)
                                                        values(ctr) = rnd.Next()
                                                    Next
                                                    Return values
                                                End Function). _
                          ContinueWith(Function(x)
                                           Dim n As Integer = x.Result.Length
                                           Dim sum As Long
                                           Dim mean As Double

                                           For ctr = 0 To x.Result.GetUpperBound(0)
                                               sum += x.Result(ctr)
                                           Next
                                           mean = sum / n
                                           Return Tuple.Create(n, sum, mean)
                                       End Function). _
                          ContinueWith(Function(x)
                                           Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                              x.Result.Item1, x.Result.Item2,
                                                              x.Result.Item3)
                                       End Function)
        Console.WriteLine(displayData.Result)
    End Sub
End Module
' The example displays output like the following:
'   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Die ContinueWhenAll-Methode und die ContinueWhenAny-Methode ermöglichen es Ihnen, die Ausführung von mehreren Aufgaben fortzusetzen.

Weitere Informationen finden Sie unter Verketten von Aufgaben mithilfe von Fortsetzungsaufgaben.

Erstellen von getrennten untergeordneten Aufgaben

Wenn durch Benutzercode, der in einer Aufgabe ausgeführt wird, eine neue Aufgabe ohne Angabe der AttachedToParent-Option erstellt wird, ist die neue Aufgabe auf keine besondere Weise mit der übergeordneten Aufgabe synchronisiert. Dieser Typ einer nicht synchronisierten Aufgabe wird als getrennte geschachtelte Aufgabe oder getrennte untergeordnete Aufgabe bezeichnet. Im folgenden Beispiel wird eine Aufgabe dargestellt, die eine getrennte untergeordnete Aufgabe erstellt.

var outer = Task.Factory.StartNew(() =>
{
    Console.WriteLine("Outer task beginning.");

    var child = Task.Factory.StartNew(() =>
    {
        Thread.SpinWait(5000000);
        Console.WriteLine("Detached task completed.");
    });
});

outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
//    Outer task beginning.
//    Outer task completed.
//    Detached task completed.
Dim outer = Task.Factory.StartNew(Sub()
                                      Console.WriteLine("Outer task beginning.")
                                      Dim child = Task.Factory.StartNew(Sub()
                                                                            Thread.SpinWait(5000000)
                                                                            Console.WriteLine("Detached task completed.")
                                                                        End Sub)
                                  End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
' The example displays the following output:
'     Outer task beginning.
'     Outer task completed.
'    Detached child completed.

Beachten Sie, dass die übergeordnete Aufgabe nicht auf den Abschluss der getrennten untergeordneten Aufgabe wartet.

Erstellen von untergeordneten Aufgaben

Wenn durch Benutzercode, der in einer Aufgabe ausgeführt wird, eine Aufgabe mit der AttachedToParent-Option erstellt wird, wird die neue Aufgabe als angefügte untergeordnete Aufgabe der übergeordneten Aufgabe bezeichnet. Mithilfe der AttachedToParent-Option können Sie eine strukturierte Aufgabenparallelität angeben, da die übergeordnete Aufgabe implizit auf den Abschluss aller angefügten untergeordneten Aufgaben wartet. Im folgenden Beispiel wird eine übergeordnete Aufgabe dargestellt, die zehn angefügte untergeordnete Aufgaben erstellt. Beachten Sie, dass, obwohl das Beispiel die Task.Wait-Methode aufruft, um auf den Abschluss der übergeordneten Aufgabe zu warten, nicht explizit auf den Abschluss der angefügten untergeordneten Aufgabe gewartet werden muss.

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 beginning.");
                      for (int ctr = 0; ctr < 10; ctr++) {
                         int taskNo = ctr;
                         Task.Factory.StartNew((x) => {
                                                  Thread.SpinWait(5000000);
                                                  Console.WriteLine("Attached child #{0} completed.",
                                                                    x);
                                               },
                                               taskNo, TaskCreationOptions.AttachedToParent);
                      }
                   });

      parent.Wait();
      Console.WriteLine("Parent task completed.");
   }
}
// The example displays output like the following:
//       Parent task beginning.
//       Attached child #9 completed.
//       Attached child #0 completed.
//       Attached child #8 completed.
//       Attached child #1 completed.
//       Attached child #7 completed.
//       Attached child #2 completed.
//       Attached child #6 completed.
//       Attached child #3 completed.
//       Attached child #5 completed.
//       Attached child #4 completed.
//       Parent task completed.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim parent = Task.Factory.StartNew(Sub()
                                               Console.WriteLine("Parent task beginning.")
                                               For ctr As Integer = 0 To 9
                                                   Dim taskNo As Integer = ctr
                                                   Task.Factory.StartNew(Sub(x)
                                                                             Thread.SpinWait(5000000)
                                                                             Console.WriteLine("Attached child #{0} completed.",
                                                                                               x)
                                                                         End Sub,
                                                                         taskNo, TaskCreationOptions.AttachedToParent)
                                               Next
                                           End Sub)
        parent.Wait()
        Console.WriteLine("Parent task completed.")
    End Sub
End Module
' The example displays output like the following:
'       Parent task beginning.
'       Attached child #9 completed.
'       Attached child #0 completed.
'       Attached child #8 completed.
'       Attached child #1 completed.
'       Attached child #7 completed.
'       Attached child #2 completed.
'       Attached child #6 completed.
'       Attached child #3 completed.
'       Attached child #5 completed.
'       Attached child #4 completed.
'       Parent task completed.

Durch das Angeben der TaskCreationOptions.DenyChildAttach-Option für eine übergeordnete Aufgabe kann das Anfügen anderer Aufgaben an die übergeordnete Aufgabe verhindert werden. Weitere Informationen finden Sie unter Angefügte und getrennte untergeordnete Aufgaben.

Warten auf die Beendigung von Aufgaben

Der System.Threading.Tasks.Task-Typ und der System.Threading.Tasks.Task<TResult>-Typ bieten mehrere Überladungen der Task.Wait-Methode, die das Warten auf den Abschluss einer Aufgabe ermöglichen. Außerdem können Sie mittels Überladungen der statischen Task.WaitAll-Methode und der Task.WaitAny-Methode auf den Abschluss einer bestimmten oder aller Aufgaben in einem Array warten.

In der Regel wird aus einem der folgenden Gründe auf den Abschluss einer Aufgabe gewartet:

  • Der Hauptthread hängt von dem Endergebnis ab, das von einer Aufgabe berechnet wird.

  • Sie müssen Ausnahmen behandeln, die möglicherweise von der Aufgabe ausgelöst werden.

  • Die Anwendung wird möglicherweise beendet, ehe die Ausführung aller Aufgaben abgeschlossen ist. Beispielsweise werden Konsolenanwendungen beendet, sobald der synchrone Code in Main (dem Anwendungseinstiegspunkt) ausgeführt wurde.

Im folgenden Beispiel wird das grundlegende Muster dargestellt, das keine Ausnahmebehandlung beinhaltet.

Task[] tasks = new Task[3]
{
    Task.Factory.StartNew(() => MethodA()),
    Task.Factory.StartNew(() => MethodB()),
    Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.
Task.WaitAll(tasks);

// Continue on this thread...
Dim tasks() =
{
    Task.Factory.StartNew(Sub() MethodA()),
    Task.Factory.StartNew(Sub() MethodB()),
    Task.Factory.StartNew(Sub() MethodC())
}

' Block until all tasks complete.
Task.WaitAll(tasks)

' Continue on this thread...

Ein Beispiel, in dem die Behandlung von Ausnahmen veranschaulicht wird, finden Sie unter Ausnahmebehandlung.

Bei einigen Überladungen können Sie einen Timeout angeben, während andere ein zusätzliches CancellationToken als Eingabeparameter nutzen, sodass der Wartevorgang selbst entweder programmgesteuert oder als Reaktion auf eine Benutzereingabe abgebrochen werden kann.

Beim Warten auf eine Aufgabe wird implizit auf alle untergeordneten Elemente dieser Aufgabe gewartet, die mit der TaskCreationOptions.AttachedToParent-Option erstellt wurden. Task.Wait wird sofort zurückgegeben, wenn die Aufgabe bereits abgeschlossen wurde. Alle von einer Aufgabe ausgelöste Ausnahmen werden von einer Task.Wait-Methode ausgelöst, auch wenn die Task.Wait-Methode nach Abschluss der Aufgabe aufgerufen wurde.

Verfassen von Aufgaben

Die Klassen Task und Task<TResult> bieten mehrere Methoden zur einfacheren Erstellung mehrerer Aufgaben, um allgemeine Muster zu implementieren und die in C#, Visual Basic sowie F# bereitgestellten asynchronen Sprachfeatures besser einzusetzen. In diesem Abschnitt werden die Methoden WhenAll, WhenAny, Delay und FromResult beschrieben.

Task.WhenAll

Die Task.WhenAll-Methode wartet asynchron auf den Abschluss mehrerer Task-Objekte oder Task<TResult>-Objekte. Die Methode stellt überladene Versionen zum Warten auf nicht einheitliche Sätze von Aufgaben bereit. Beispielsweise kann in einem Methodenaufruf auf den Abschluss mehrerer Task-Objekte und Task<TResult>-Objekte gewartet werden.

Task.WhenAny

Die Task.WhenAny-Methode wartet asynchron auf den Abschluss eines von mehreren Task-Objekten oder Task<TResult>-Objekten. Wie die Task.WhenAll-Methode stellt auch diese Methode überladene Versionen bereit, mit denen auf nicht einheitliche Sätze von Aufgaben gewartet werden kann. Die WhenAny-Methode ist insbesondere in folgenden Szenarien nützlich.

  • Redundante Vorgänge. Betrachten Sie einen Algorithmus oder einen Vorgang, der auf verschiedene Weise ausgeführt werden kann. Sie können die WhenAny-Methode verwenden, um den Vorgang auszuwählen, der zuerst beendet wird, und dann die verbleibenden Vorgänge abzubrechen.

  • Überlappende Vorgänge. Sie können mehrere Vorgänge starten, die alle beendet werden müssen, und die WhenAny-Methode verwenden, um Ergebnisse zu verarbeiten, wenn jeder Vorgang beendet wird. Nachdem ein Vorgang beendet wurde, können Sie eine oder mehrere weitere Aufgaben starten.

  • Eingeschränkte Vorgänge. Sie können die WhenAny-Methode verwenden, um das vorherige Szenario zu erweitern, indem Sie die Anzahl der gleichzeitigen Vorgänge einschränken.

  • Abgelaufene Vorgänge. Sie können die WhenAny-Methode verwenden, um eine Auswahl zwischen einer oder mehreren Aufgaben und einer anderen Aufgabe zu treffen, die nach einem bestimmten Zeitpunkt abgeschlossen wird, z. B. die von der Delay-Methode zurückgegebene Aufgabe. Die Delay-Methode wird im folgenden Abschnitt beschrieben.

Task.Delay

Die Task.Delay-Methode generiert Task-Objekt, das nach dem angegebenen Zeitraum abgeschlossen wird. Mithilfe dieser Methode können Sie Schleifen erstellen, die gelegentlich Daten abrufen, Timeouts einfügen, die Verarbeitung von Benutzereingaben für einen vorab festgelegten Zeitraum verzögern usw.

Task(T).FromResult

Mit der Task.FromResult-Methode, können Sie ein Task<TResult>-Objekt erstellen, das ein vorberechnetes Ergebnis enthält. Diese Methode ist nützlich, wenn Sie einen asynchronen Vorgang ausführen, der ein Task<TResult>-Objekt zurückgibt, und das Ergebnis dieses Task<TResult>-Objekts bereits berechnet wurde. Ein Beispiel, das FromResult verwendet, um die Ergebnisse asynchroner Downloadvorgänge aus einem Cache abzurufen, finden Sie unter Vorgehensweise: Erstellen von vorberechneten Aufgaben.

Behandeln von Ausnahmen in Aufgaben

Wenn eine Aufgabe eine oder mehrere Ausnahmen auslöst, werden die Ausnahmen in eine AggregateException-Ausnahme eingeschlossen. Diese Ausnahme wird an den Thread zurückgegeben, der mit der Aufgabe verbunden ist. In der Regel ist dies der Thread, der auf den Abschluss der Aufgabe wartet oder der Thread, der auf die Result-Eigenschaft zugreift. Dieses Verhalten trägt dazu bei, dass die .NET Framework-Richtlinie umgesetzt wird, der zufolge alle Ausnahmefehler standardmäßig zu einem Beenden des Prozesses führen. Der aufrufende Code kann die Ausnahmen behandeln, indem er Folgendes in einem try/catch-Block verwendet:

Der Verbindungsthread kann Ausnahmen ebenfalls behandeln, indem er auf die Exception-Eigenschaft zugreift, bevor die Aufgabe der Garbage Collection zugeordnet wird. Durch den Zugriff auf diese Eigenschaft verhindern Sie, dass der Ausnahmefehler das Ausnahmeweitergabe-Verhalten auslöst, durch das der Prozess beim Abschluss des Objekts beendet wird.

Weitere Informationen zu Ausnahmen und Aufgaben finden Sie unter Ausnahmebehandlung.

Abbrechen von Aufgaben

Die Task-Klasse unterstützt einen kooperativen Abbruch und ist vollständig in die System.Threading.CancellationTokenSource-Klasse und die System.Threading.CancellationToken-Klasse integriert, die in .NET Framework 4 eingeführt wurden. Viele Konstruktoren in der System.Threading.Tasks.Task-Klasse verwenden ein CancellationToken-Objekt als Eingabeparameter. Viele der StartNew- und Run-Überladungen enthalten auch einen CancellationToken-Parameter.

Mit der CancellationTokenSource-Klasse können Sie das Token erstellen und die Abbruchanforderung zu einem späteren Zeitpunkt ausgeben. Übergeben Sie das Token als Argument an Task, und verweisen Sie in dem Benutzerdelegaten, der auf eine Abbruchanforderung reagiert, auf das gleiche Token.

Weitere Informationen finden Sie unter Aufgabenabbruch und Vorgehensweise: Abbrechen einer Aufgabe und ihrer untergeordneten Elemente.

Die TaskFactory-Klasse

Die TaskFactory-Klasse stellt statische Methoden bereit, die einige allgemeine Muster zum Erstellen und Starten von Aufgaben und Fortsetzungsaufgaben kapseln.

Auf die standardmäßige TaskFactory kann als statische Eigenschaft in der Task-Klasse oder Task<TResult>-Klasse zugegriffen werden. Sie können eine TaskFactory auch direkt instanziieren und verschiedene Optionen angeben, einschließlich CancellationToken, TaskCreationOptions, TaskContinuationOptions oder TaskScheduler. Die beim Erstellen der Aufgabenfactory angegebenen Optionen werden auf alle Aufgaben angewendet, die von dieser erstellt werden, außer Sie erstellen Task mit der TaskCreationOptions-Enumeration. In diesem Fall werden die Optionen der Aufgabenfactory mit den Optionen der Aufgabe überschrieben.

Aufgaben ohne Delegaten

In einigen Fällen können Sie mithilfe einer Task einen asynchronen Vorgang kapseln, der nicht von Ihrem Benutzerdelegaten, sondern von einer externen Komponente durchgeführt wird. Wenn der Vorgang auf dem Begin/End-Muster des asynchronen Programmiermodells basiert, können Sie die FromAsync-Methoden verwenden. Wenn das nicht der Fall ist, können Sie den Vorgang mithilfe des TaskCompletionSource<TResult>-Objekts in eine Aufgabe einschließen und dadurch bestimmte Vorteile der Task-Programmierbarkeit erhalten, z. B. Unterstützung von Ausnahmeweitergabe und Fortsetzungen. Weitere Informationen finden Sie unter TaskCompletionSource<TResult>.

Benutzerdefinierte Planer

Die meisten Anwendungs- oder Bibliotheksentwickler machen sich keine Gedanken über den für die Ausführung der Aufgabe verwendeten Prozessor, über die Synchronisierung der Arbeit mit anderen Aufgaben oder die Planung im System.Threading.ThreadPool. Die einzige Voraussetzung für sie ist eine möglichst effiziente Ausführung auf dem Hostcomputer. Wenn Sie eine präzisere Steuerung der Planungsdetails benötigen, können Sie in der Task Parallel Library bestimmte Einstellungen im Standardaufgabenplaner konfigurieren und sogar einen benutzerdefinierten Planer angeben. Weitere Informationen finden Sie unter TaskScheduler.

Die TPL beinhaltet zahlreiche neue öffentliche Typen, die sowohl in parallelen, als auch in sequenziellen Szenarios hilfreich sind. Hierzu zählen mehrere threadsichere, schnelle und skalierbare Auflistungsklassen im System.Collections.Concurrent-Namespace sowie mehrere neue Synchronisierungstypen, z. B. System.Threading.Semaphore und System.Threading.ManualResetEventSlim, die für bestimmte Arten von Arbeitslasten effizienter als ihre Vorgänger sind. Andere neue Typen in .NET Framework 4, z. B. System.Threading.Barrier und System.Threading.SpinLock, stellen Funktionalität bereit, die in früheren Releases nicht verfügbar war. Weitere Informationen finden Sie unter Datenstrukturen für die parallele Programmierung.

Benutzerdefinierte Aufgabentypen

Es wird empfohlen, nicht von System.Threading.Tasks.Task oder System.Threading.Tasks.Task<TResult> zu erben. Stattdessen sollten Sie die AsyncState-Eigenschaft verwenden, um einem Task-Objekt oder einem Task<TResult>-Objekt zusätzliche Daten oder einen zusätzlichen Zustand zuzuordnen. Sie können auch Erweiterungsmethoden verwenden, um die Funktionen der Task-Klasse und der Task<TResult>-Klasse zu erweitern. Weitere Informationen zu Erweiterungsmethoden finden Sie unter Erweiterungsmethoden und Erweiterungsmethoden.

Wenn Sie von Task oder Task<TResult> erben müssen, können Sie nicht Run oder die Klassen System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult> oder System.Threading.Tasks.TaskCompletionSource<TResult> zum Erstellen von Instanzen des benutzerdefinierten Aufgabentyps verwenden, da von diesen Mechanismen nur Task- und Task<TResult>-Objekte erstellt werden. Darüber hinaus können Sie die Aufgaben-Fortsetzungsmechanismen nicht verwenden, die von Task, , TaskFactoryTask<TResult>und TaskFactory<TResult> zum Erstellen von Instanzen Ihres benutzerdefinierten Aufgabentyps bereitgestellt werden, da diese Mechanismen auch nur Task und Task<TResult> Objekte erstellen.

Titel Beschreibung
Verketten von Aufgaben mithilfe von Fortsetzungsaufgaben Beschreibt die Funktionsweise von Fortsetzungen.
Angefügte und getrennte untergeordnete Aufgaben Beschreibt den Unterschied zwischen angefügten und getrennten untergeordneten Aufgaben.
Aufgabenabbruch Beschreibt die integrierte Abbruchunterstützung des Task-Objekts.
Ausnahmebehandlung Beschreibt die Behandlung von Ausnahmen in gleichzeitigen Threads.
How to: Ausführen von parallelen Vorgängen mithilfe von „Parallel.Invoke“ Beschreibt die Verwendung von Invoke.
How to: Zurückgeben eines Werts aus einer Aufgabe Beschreibt, wie in Aufgaben Werte zurückgegeben werden.
How to: Abbrechen einer Aufgabe und ihrer untergeordneten Elemente Beschreibt, wie Aufgaben abgebrochen werden.
How to: Erstellen von vorberechneten Aufgaben Beschreibt, wie mithilfe der Task.FromResult-Methode die Ergebnisse asynchroner Downloadvorgänge aus einem Cache abgerufen werden können.
How to: Durchlaufen einer binären Struktur mit parallelen Aufgaben Beschreibt, wie Aufgaben zum Traversieren einer binären Struktur verwendet werden.
How to: Entpacken einer geschachtelten Aufgabe Veranschaulicht die Verwendung der Unwrap-Erweiterungsmethode.
Datenparallelität Beschreibt, wie Sie mithilfe von For und ForEach parallele Schleifen für Daten erstellen.
Parallele Programmierung Der Knoten auf oberster Ebene für die parallele .NET Framework-Programmierung.

Siehe auch