Aufgabenbasierte asynchrone Programmierung
Die Task Parallel Library (TPL) basiert auf dem Konzept einer Aufgabe, die einen asynchronen Vorgang darstellt. In gewisser Hinsicht ähnelt eine Aufgabe einem Thread oder einem ThreadPool Aufgabenobjekt, jedoch auf einer höheren Abstraktionsebene. Der Begriff Vorgangsparallelität bezieht sich auf eine oder mehrere unabhängige Aufgaben, die gleichzeitig ausgeführt werden. Aufgaben bieten zwei hauptvorteile:
Effizientere und skalierbarere Nutzung von Systemressourcen.
Aufgaben werden im Hintergrund in den ThreadPool eingestellt, der mit Algorithmen verbessert wurde, durch die die Anzahl von Threads bestimmt und angepasst wird. Diese Algorithmen bieten einen Lastenausgleich, um den Durchsatz zu maximieren. Dieser Vorgang macht Aufgaben relativ leicht, und Sie können viele davon erstellen, um feinkörnige Parallelität zu ermöglichen.
Mehr programmgesteuerte Steuerung, als mit einem Thread oder einer Arbeitsaufgabe möglich ist.
Aufgaben und das darauf basierende Framework bieten eine umfangreiche Gruppe von APIs, die Warten, Abbruch, Fortsetzungen, robuste Ausnahmebehandlung, detaillierten Status, benutzerdefinierte Planung und vieles mehr unterstützen.
Aus beiden Gründen ist TPL die bevorzugte API zum Schreiben von Multithreads, asynchronen und parallelen Code in .NET.
Implizites Erstellen und Ausführen von Aufgaben
Die Parallel.Invoke-Methode bietet eine bequeme Möglichkeit, beliebig viele beliebige Anweisungen gleichzeitig auszuführen. Sie müssen nur einen Action-Delegaten für jede Arbeitsaufgabe übergeben. Die einfachste Möglichkeit zum Erstellen dieser Stellvertretungen besteht darin, Lambda-Ausdrücke zu verwenden. Der Lambda-Ausdruck kann entweder eine benannte Methode aufrufen oder den Code inline bereitstellen. Das folgende Beispiel zeigt einen einfachen Invoke Aufruf, der zwei Aufgaben erstellt und startet, die gleichzeitig ausgeführt werden. Die erste Aufgabe wird durch einen Lambda-Ausdruck dargestellt, der eine Methode namens DoSomeWork
aufruft, und die zweite Aufgabe wird durch einen Lambda-Ausdruck dargestellt, der eine Methode namens DoSomeOtherWork
aufruft.
Anmerkung
In dieser Dokumentation werden Lambda-Ausdrücke verwendet, um Stellvertretungen in TPL zu definieren. Wenn Sie mit Lambda-Ausdrücken in C# oder Visual Basic nicht vertraut sind, lesen Sie Lambda-Ausdrücke in PLINQ- und TPL-.
Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())
Anmerkung
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 durchführen, insbesondere bei einer großen Anzahl von Delegaten.
Weitere Informationen finden Sie unter So verwenden Sie Parallel.Invoke, um parallele Operationen auszuführen.
Um eine bessere Kontrolle über die Aufgabenausführung zu erhalten oder einen Wert aus der Aufgabe zurückzugeben, müssen Sie mit Task Objekten expliziter arbeiten.
Explizites Erstellen und Ausführen von Aufgaben
Eine Aufgabe, die keinen Wert zurückgibt, wird durch die System.Threading.Tasks.Task Klasse dargestellt. Ein Vorgang, der einen Wert zurückgibt, wird durch die System.Threading.Tasks.Task<TResult> Klasse dargestellt, die von Taskerbt. Das Aufgabenobjekt behandelt die Infrastrukturdetails und stellt Methoden und Eigenschaften bereit, auf die über den aufrufenden Thread während der gesamten Lebensdauer der Aufgabe zugegriffen werden kann. Sie können z. B. jederzeit auf die Status Eigenschaft einer Aufgabe zugreifen, um festzustellen, ob die Ausführung gestartet wurde, ob sie abgeschlossen wurde, abgebrochen oder eine Ausnahme ausgelöst hat. Der Status wird durch eine TaskStatus Enumeration dargestellt.
Wenn Sie eine Aufgabe erstellen, geben Sie ihm einen Benutzerdelegat, der den Code kapselt, den die Aufgabe ausführt. Der Delegat kann als benannter Delegat, als anonyme Methode oder als Lambda-Ausdruck angegeben werden. Lambda-Ausdrücke können einen Aufruf einer benannten Methode enthalten, wie im folgenden Beispiel gezeigt. Das Beispiel enthält einen Aufruf der Task.Wait-Methode, um sicherzustellen, dass die Aufgabe die Ausführung abgeschlossen hat, bevor die Konsolenmodusanwendung beendet wird.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Lambda
{
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 as follows:
// Hello from thread 'Main'.
// Hello from taskA.
// or
// Hello from taskA.
// Hello from thread 'Main'.
Imports System.Threading
Namespace Lambda
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.
End Namespace
Sie können auch die Task.Run Methoden verwenden, um eine Aufgabe in einem Schritt zu erstellen und zu starten. Zum Verwalten des Vorgangs verwenden die Run Methoden den Standardaufgabenplaner, unabhängig davon, welcher Aufgabenplaner dem aktuellen Thread zugeordnet ist. Die Run-Methoden sind die bevorzugte Methode zum Erstellen und Starten von Vorgängen, wenn keine zusätzliche Kontrolle über die Erstellung und Planung des Vorgangs erforderlich ist.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Run;
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 as follows:
// Hello from thread 'Main'.
// Hello from taskA.
// or
// Hello from taskA.
// Hello from thread 'Main'.
Imports System.Threading
Namespace Run
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.
End Namespace
Sie können auch die TaskFactory.StartNew-Methode verwenden, um eine Aufgabe in einem Einzigen Vorgang zu erstellen und zu starten. Wie im folgenden Beispiel gezeigt, können Sie diese Methode verwenden, wenn:
Die Erstellung und Planung müssen nicht getrennt werden, und Sie benötigen zusätzliche Aufgabenerstellungsoptionen oder die Verwendung eines bestimmten Zeitplans.
Sie müssen zusätzlichen Zustand an die Aufgabe übergeben, den Sie über die Task.AsyncState-Eigenschaft abrufen können.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskIntro;
class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}
public class AsyncState
{
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, ran on thread #3.
// Task #1 created at 635116412924607584, ran on thread #4.
// Task #2 created at 635116412924607584, ran on thread #4.
// Task #3 created at 635116412924607584, ran on thread #4.
// Task #4 created at 635116412924607584, ran on thread #3.
// Task #5 created at 635116412924607584, ran on thread #3.
// Task #6 created at 635116412924607584, ran on thread #4.
// Task #7 created at 635116412924607584, ran on thread #4.
// Task #8 created at 635116412924607584, ran on thread #3.
// Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading
Namespace AsyncState
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 = Environment.CurrentManagedThreadId
End Sub,
New CustomData With {.Name = i, .CreationTime = Date.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 635116412924597583, ran on thread #3.
' Task #1 created at 635116412924607584, ran on thread #4.
' Task #2 created at 635116412924607584, ran on thread #4.
' Task #3 created at 635116412924607584, ran on thread #4.
' Task #4 created at 635116412924607584, ran on thread #3.
' Task #5 created at 635116412924607584, ran on thread #3.
' Task #6 created at 635116412924607584, ran on thread #4.
' Task #7 created at 635116412924607584, ran on thread #4.
' Task #8 created at 635116412924607584, ran on thread #3.
' Task #9 created at 635116412924607584, ran on thread #4.
End Namespace
Task und Task<TResult> besitzen jeweils eine statische Eigenschaft Factory, die eine Standardinstanz von TaskFactoryzurückgibt, sodass Sie die Methode als Task.Factory.StartNew()
aufrufen können. Außerdem hat im folgenden Beispiel jede Aufgabe, da sie vom Typ System.Threading.Tasks.Task<TResult>ist, eine öffentliche Task<TResult>.Result-Eigenschaft, die das Ergebnis der Berechnung enthält. Die Aufgaben werden asynchron ausgeführt und können in beliebiger Reihenfolge ausgeführt werden. Wenn auf die Result-Eigenschaft zugegriffen wird, bevor die Berechnung abgeschlossen ist, blockiert die Eigenschaft den aufrufenden Thread, bis der Wert verfügbar ist.
using System;
using System.Threading.Tasks;
public class Result
{
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
Namespace Result
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
End Namespace
Weitere Informationen finden Sie unter Wie man einen Wert von einem Task zurückgibt.
Wenn Sie einen Lambda-Ausdruck verwenden, um einen Delegaten zu erstellen, haben Sie Zugriff auf alle Variablen, die zu diesem Zeitpunkt im Quellcode sichtbar sind. In einigen Fällen, insbesondere innerhalb von Schleifen, erfasst eine Lambda-Funktion die Variable jedoch nicht wie erwartet. Es erfasst nur die Referenz der Variablen, nicht den Wert, da sie sich nach jeder Iteration ändert. Das folgende Beispiel veranschaulicht das Problem. Ein Schleifenzähler wird an einen Lambda-Ausdruck übergeben, der ein CustomData
-Objekt instanziiert und den Schleifenzähler als Bezeichner des Objekts verwendet. Wie die Ausgabe aus dem Beispiel zeigt, verfügt jedes CustomData
-Objekt über einen identischen Bezeichner.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Example.Iterations;
class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}
public class IterationTwo
{
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
Namespace IterationsTwo
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 = Date.Now.Ticks}
data.ThreadNum = Environment.CurrentManagedThreadId
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.
End Namespace
Sie können auf den Wert für jede Iteration zugreifen, indem Sie über den Konstruktor ein Statusobjekt für eine Aufgabe bereitstellen. Im folgenden Beispiel wird das vorherige 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 aus dem Beispiel zeigt, verfügt jedes CustomData
-Objekt jetzt über einen eindeutigen Bezeichner basierend auf dem Wert des Schleifenzählers zum Zeitpunkt der Instanziierung des Objekts.
using System;
using System.Threading;
using System.Threading.Tasks;
class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}
public class IterationOne
{
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
Namespace IterationsOne
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 = Environment.CurrentManagedThreadId
Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum)
End Sub,
New CustomData With {.Name = i, .CreationTime = Date.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.
End Namespace
Dieser Zustand wird als Argument an den Aufgabendelegat übergeben und kann über das Aufgabenobjekt mithilfe der Task.AsyncState-Eigenschaft darauf zugegriffen werden. Das folgende Beispiel ist eine Variation des vorherigen Beispiels. Sie verwendet die AsyncState-Eigenschaft, um Informationen zu den an den Lambda-Ausdruck übergebenen CustomData
Objekten anzuzeigen.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskIntro;
class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}
public class AsyncState
{
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, ran on thread #3.
// Task #1 created at 635116412924607584, ran on thread #4.
// Task #2 created at 635116412924607584, ran on thread #4.
// Task #3 created at 635116412924607584, ran on thread #4.
// Task #4 created at 635116412924607584, ran on thread #3.
// Task #5 created at 635116412924607584, ran on thread #3.
// Task #6 created at 635116412924607584, ran on thread #4.
// Task #7 created at 635116412924607584, ran on thread #4.
// Task #8 created at 635116412924607584, ran on thread #3.
// Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading
Namespace AsyncState
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 = Environment.CurrentManagedThreadId
End Sub,
New CustomData With {.Name = i, .CreationTime = Date.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 635116412924597583, ran on thread #3.
' Task #1 created at 635116412924607584, ran on thread #4.
' Task #2 created at 635116412924607584, ran on thread #4.
' Task #3 created at 635116412924607584, ran on thread #4.
' Task #4 created at 635116412924607584, ran on thread #3.
' Task #5 created at 635116412924607584, ran on thread #3.
' Task #6 created at 635116412924607584, ran on thread #4.
' Task #7 created at 635116412924607584, ran on thread #4.
' Task #8 created at 635116412924607584, ran on thread #3.
' Task #9 created at 635116412924607584, ran on thread #4.
End Namespace
Vorgangs-ID
Jede Aufgabe erhält eine ganzzahlige ID, die sie in einer Anwendungsdomäne eindeutig identifiziert und auf die über die 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 erstellt, was bedeutet, dass sie erst erstellt wird, wenn sie angefordert wird. Daher kann eine Aufgabe jedes Mal, wenn das Programm ausgeführt wird, eine andere ID aufweisen. Weitere Informationen darüber, wie Sie Aufgaben-IDs im Debugger anzeigen können, finden Sie unter Verwenden des Aufgabenfensters und Verwenden des Parallelstapel-Fensters.
Aufgabenerstellungsoptionen
Die meisten APIs, die Aufgaben erstellen, stellen Überladungen bereit, die einen TaskCreationOptions Parameter akzeptieren. Wenn Sie eine oder mehrere dieser Optionen angeben, teilen Sie dem Aufgabenplaner mit, wie der Vorgang im Threadpool geplant wird. Optionen können mithilfe eines bitweisen ODER--Vorgangs kombiniert werden.
Das folgende Beispiel zeigt eine Aufgabe mit den Optionen LongRunning und PreferFairness:
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 verfügt über eine zugeordnete Kultur- und UI-Kultur, die durch die eigenschaften Thread.CurrentCulture bzw. Thread.CurrentUICulture definiert werden. Die Kultur eines Threads wird in Vorgängen wie Formatierung, Analyse, Sortierung und Zeichenfolgenvergleichsvorgängen verwendet. Die UI-Kultur eines Threads wird in der Ressourcensuche verwendet.
Die Systemkultur definiert die Standardkultur und UI-Kultur eines Threads. Sie können jedoch eine Standardkultur für alle Threads in einer Anwendungsdomäne angeben, indem Sie die eigenschaften CultureInfo.DefaultThreadCurrentCulture und CultureInfo.DefaultThreadCurrentUICulture verwenden. Wenn Sie die Kultur eines Threads explizit festlegen und einen neuen Thread starten, erbt der neue Thread nicht die Kultur des aufrufenden Threads. Stattdessen ist ihre Kultur die Standardsystemkultur. Bei der aufgabenbasierten Programmierung verwenden Aufgaben jedoch die Kultur des aufrufenden Threads, auch wenn die Aufgabe asynchron in einem anderen Thread ausgeführt wird.
Das folgende Beispiel enthält eine einfache Abbildung. Es ändert die aktuelle Kultur der App in Französisch (Frankreich). Wenn Französisch (Frankreich) bereits die aktuelle Kultur ist, ändert es sie in Englisch (USA). Anschließend wird ein Delegat namens 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 €
Anmerkung
In Versionen von .NET Framework vor .NET Framework 4.6 wird die Kultur einer Aufgabe durch die Kultur des Threads bestimmt, auf dem sie ausgeführt wird, nicht von der Kultur des aufrufenden Threads. Bei asynchronen Aufgaben kann sich die von der Aufgabe verwendete Kultur von der Kultur des aufrufenden Threads unterscheiden.
Weitere Informationen zu asynchronen Aufgaben und Kultur finden Sie im Abschnitt "Kultur und asynchrone aufgabenbasierte Vorgänge" im CultureInfo Artikel.
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. Der Delegierte der Fortsetzungsaufgabe wird mit einem Verweis auf die vorangegangene Aufgabe ausgestattet, damit er den Status dieser Aufgabe untersuchen kann. Wenn Sie den Wert der Task<TResult>.Result-Eigenschaft abrufen, können Sie die Ausgabe des Vorgängers als Eingabe für die Fortsetzung verwenden.
Im folgenden Beispiel wird die getData
Aufgabe durch einen Aufruf der TaskFactory.StartNew<TResult>(Func<TResult>)-Methode gestartet. Der processData
Vorgang wird automatisch gestartet, wenn getData
abgeschlossen ist, und displayData
wird gestartet, wenn processData
abgeschlossen ist. getData
erzeugt ein Integer-Array, auf das die processData
-Aufgabe über die Task<TResult>.Result-Eigenschaft der getData
-Aufgabe zugreifen 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 wird. Die displayData
Aufgabe wird automatisch ausgeführt, wenn processData
abgeschlossen ist, und das vom processData
Lambda-Ausdruck zurückgegebene Tuple<T1,T2,T3>-Objekt ist für die displayData
Aufgabe über die Task<TResult>.Result-Eigenschaft der processData
Aufgabe zugänglich. Der displayData
Vorgang übernimmt das Ergebnis des processData
Vorgangs. Es erzeugt ein Ergebnis, dessen Typ auf ähnliche Weise abgeleitet wird und dem Programm in der Result-Eigenschaft zur Verfügung gestellt wird.
using System;
using System.Threading.Tasks;
public class ContinuationOne
{
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
Namespace ContinuationsOne
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
End Namespace
Da es sich bei Task.ContinueWith um eine Instanzmethode handelt, können Sie Methodenaufrufe miteinander verketten, anstatt ein Task<TResult> Objekt für jede vorhergehende Aufgabe zu instanziieren. Das folgende Beispiel ist funktionell identisch mit dem vorherigen, mit der Ausnahme, dass es Aufrufe an die Task.ContinueWith-Methode miteinander verkettet. Das Task<TResult> Objekt, das von der Kette der Methodenaufrufe zurückgegeben wird, ist die letzte Fortsetzungsaufgabe.
using System;
using System.Threading.Tasks;
public class ContinuationTwo
{
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
Namespace ContinuationsTwo
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
End Namespace
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 Benutzercode, der in einer Aufgabe ausgeführt wird, eine neue Aufgabe erstellt und die Option AttachedToParent nicht angibt, wird die neue Aufgabe nicht auf 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.
Anmerkung
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 10 angefügte untergeordnete Aufgaben erstellt. Im Beispiel wird die Task.Wait-Methode aufgerufen, um zu warten, bis die übergeordnete Aufgabe abgeschlossen ist. Es muss aber nicht explizit auf den Abschluss der angefügten untergeordneten Aufgaben gewartet werden.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Child
{
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
Namespace Child
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.
End Namespace
Eine übergeordnete Aufgabe kann die Option TaskCreationOptions.DenyChildAttach verwenden, um zu verhindern, dass andere Aufgaben an die übergeordnete Aufgabe angefügt werden. Weitere Informationen finden Sie unter Angefügte und getrennte untergeordnete Aufgaben.
Warten auf den Abschluss von Vorgängen
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.
Normalerweise würden Sie auf eine Aufgabe aus einem der folgenden Gründe warten:
Der Hauptthread hängt vom Endergebnis ab, das von einem Vorgang berechnet wird.
Sie müssen Ausnahmen behandeln, die möglicherweise von der Aufgabe ausgelöst werden.
Die Anwendung kann beendet werden, bevor alle Aufgaben die Ausführung abgeschlossen haben. Konsolenanwendungen werden beispielsweise beendet, nachdem der synchrone Code in
Main
(der Anwendung einstiegspunkt) ausgeführt wurde.
Das folgende Beispiel zeigt das grundlegende Muster, das keine Ausnahmebehandlung umfasst:
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 für die Ausnahmebehandlung finden Sie unter Ausnahmebehandlung.
Bei einigen Überladungen können Sie ein Timeout angeben, und andere verwenden eine zusätzliche CancellationToken als Eingabeparameter, sodass die Wartezeit selbst entweder programmgesteuert oder als Reaktion auf Benutzereingaben 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. Eine Task.Wait-Methode löst alle Ausnahmen aus, die von einem Vorgang ausgelöst werden, auch wenn die Task.Wait-Methode aufgerufen wurde, nachdem die Aufgabe abgeschlossen wurde.
Verfassen von Aufgaben
Die klassen Task und Task<TResult> bieten mehrere Methoden, mit denen Sie mehrere Aufgaben erstellen können. Diese Methoden implementieren allgemeine Muster und nutzen die asynchronen Sprachmerkmale, die von C#, Visual Basic und F# bereitgestellt werden. In diesem Abschnitt werden die Methoden WhenAll, WhenAny, Delayund FromResult beschrieben.
Task.WhenAll
Die Task.WhenAll-Methode wartet asynchron, bis mehrere Task oder Task<TResult> Objekte abgeschlossen sind. 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, bis eins von mehreren Task oder Task<TResult> Objekten abgeschlossen ist. Wie bei der Task.WhenAll-Methode stellt diese Methode überladene Versionen bereit, mit denen Sie auf nicht einheitliche Aufgabengruppen warten können. Die WhenAny Methode ist in den folgenden Szenarien besonders nützlich:
redundante Vorgänge: Betrachten Sie einen Algorithmus oder vorgang, der auf vielfältige Weise ausgeführt werden kann. Sie können die WhenAny-Methode verwenden, um den Vorgang auszuwählen, der zuerst abgeschlossen ist, und dann die verbleibenden Vorgänge abbrechen.
Verschachtelte Operationen: Sie können mehrere Operationen starten, die abgeschlossen werden müssen, und die Methode WhenAny verwenden, um die Ergebnisse zu verarbeiten, wenn jede Operation abgeschlossen ist. Nach Abschluss eines Vorgangs können Sie einen oder mehrere Vorgänge starten.
Drosselungsvorgä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 zwischen einem oder mehreren Vorgängen und einem Vorgang auszuwählen, der nach einer bestimmten Zeit endet, z. B. einen Vorgang, der von der Delay-Methode zurückgegeben wird. Die Delay-Methode wird im folgenden Abschnitt beschrieben.
Task.Delay
Die Task.Delay-Methode erzeugt ein Task-Objekt, das nach der angegebenen Zeit beendet wird. Mit dieser Methode können Sie Schleifen erstellen, die Daten abfragen, Timeouts angeben, die Behandlung von Benutzereingaben verzögern usw.
Task(T).FromResult
Mithilfe der Task.FromResult-Methode können Sie ein Task<TResult>-Objekt erstellen, das ein vorab berechnetes 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 wird. Ein Beispiel, das FromResult verwendet, um die Ergebnisse asynchroner Downloadvorgänge abzurufen, die in einem Cache gespeichert sind, finden Sie unter How to: Create Pre-Computed Tasks.
Umgang mit Ausnahmen bei 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 verknüpft wird. Normalerweise wartet der Thread darauf, dass die Aufgabe abgeschlossen wird, oder er greift auf die Result-Eigenschaft zu. Dieses Verhalten dient zur Durchsetzung der .NET Framework-Richtlinie, der zufolge alle Ausnahmefehler standardmäßig zu einem Beenden des Prozesses führen. Der aufrufende Code kann die Ausnahmen mithilfe einer der folgenden Aktionen in einem try
/catch
-Block verarbeiten:
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 die unbehandelte Ausnahme das Ausnahmeverteilungsverhalten auslöst, das den Prozess beendet, wenn das Objekt abgeschlossen ist.
Weitere Informationen zu Ausnahmen und Aufgaben finden Sie unter Ausnahmebehandlung.
Abbrechen von Vorgängen
Die Task-Klasse unterstützt den kooperativen Abbruch und ist vollständig in die klassen System.Threading.CancellationTokenSource und System.Threading.CancellationToken integriert, die in .NET Framework 4 eingeführt wurden. Viele der 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.
Sie können das Token erstellen und die Abbruchanforderung zu einem späteren Zeitpunkt ausgeben, indem Sie die CancellationTokenSource Klasse verwenden. Ü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 allgemeine Muster zum Erstellen und Starten von Aufgaben und Fortsetzungsaufgaben kapseln.
Das am häufigsten verwendete Muster ist StartNew, das eine Aufgabe in einer Anweisung erstellt und startet.
Wenn Sie Fortsetzungsaufgaben aus mehreren Vorgängern erstellen, verwenden Sie die ContinueWhenAll-Methode oder ContinueWhenAny-Methode oder deren Entsprechungen in der Task<TResult>-Klasse. Weitere Informationen finden Sie unter Verketten von Aufgaben mithilfe von Fortsetzungsaufgaben.
Um Methoden des asynchronen Programmiermodells
BeginX
undEndX
in einer Task- oder Task<TResult>-Instanz zu kapseln, verwenden Sie die FromAsync-Methoden. Weitere Informationen finden Sie unter TPL und herkömmliche asynchrone .NET Framework-Programmierung.
Auf die Standard-TaskFactory kann als statische Eigenschaft der Klasse Task oder der Klasse Task<TResult> zugegriffen werden. Sie können eine TaskFactory auch direkt instanziieren und verschiedene Optionen angeben, die eine CancellationToken, eine TaskCreationOptions-Option, eine TaskContinuationOptions-Option oder eine TaskSchedulerenthalten. Alle Optionen, die beim Erstellen der Aufgabenfabrik angegeben werden, werden auf alle von ihr erstellten Aufgaben angewendet, es sei denn, die Task wird mithilfe der TaskCreationOptions-Enumeration erstellt, in diesem Fall setzen die Optionen der Aufgabe die Optionen der Aufgabenfabrik außer Kraft.
Aufgaben ohne Stellvertretungen
In einigen Fällen möchten Sie möglicherweise eine Task verwenden, um einen asynchronen Vorgang zu kapseln, der von einer externen Komponente anstelle des Benutzerdelegats ausgeführt wird. Wenn der Vorgang auf dem Muster "Begin/End" des asynchronen Programmiermodells basiert, können Sie die FromAsync Methoden verwenden. Wenn dies nicht der Fall ist, können Sie das TaskCompletionSource<TResult>-Objekt verwenden, um den Vorgang in eine Aufgabe umschließen und dadurch einige der Vorteile Task Programmierbarkeit zu erzielen. Beispielsweise Unterstützung für Ausnahmeweitergabe und Fortsetzungen. Weitere Informationen finden Sie unter TaskCompletionSource<TResult>.
Benutzerdefinierte Scheduler
Die meisten Anwendungs- oder Bibliotheksentwickler kümmern sich nicht darum, auf welchem Prozessor die Aufgabe ausgeführt wird, wie sie ihre Arbeit mit anderen Aufgaben synchronisiert oder wie sie für die System.Threading.ThreadPoolgeplant ist. Sie erfordern nur, dass sie so effizient wie möglich auf dem Hostcomputer ausgeführt wird. Wenn Sie eine genauere Kontrolle über die Planungsdetails benötigen, können Sie mit der TPL einige Einstellungen für den Standardaufgabenplaner konfigurieren und sogar einen benutzerdefinierten Zeitplan bereitstellen. Weitere Informationen finden Sie unter TaskScheduler.
Verwandte Datenstrukturen
Die TPL beinhaltet zahlreiche neue öffentliche Typen, die in parallelen und sequenziellen Szenarien nützlich sind. Dazu gehören mehrere threadsichere, schnelle und skalierbare Sammlungsklassen im System.Collections.Concurrent Namespace und mehrere neue Synchronisierungstypen. Beispielsweise System.Threading.Semaphore und System.Threading.ManualResetEventSlim, die effizienter sind als ihre Vorgänger für bestimmte Arten von Workloads. Andere neue Typen in .NET Framework 4, z. B. System.Threading.Barrier und System.Threading.SpinLock, stellen Funktionen bereit, die in früheren Versionen nicht verfügbar waren. Weitere Informationen finden Sie unter Datenstrukturen für parallele Programmierung.
Benutzerdefinierte Aufgabentypen
Es wird empfohlen, dass Sie nicht von System.Threading.Tasks.Task oder System.Threading.Tasks.Task<TResult>erben. Stattdessen wird empfohlen, die AsyncState-Eigenschaft zu verwenden, um zusätzliche Daten oder den Status einem Task- oder Task<TResult>-Objekt zuzuordnen. Sie können auch Erweiterungsmethoden verwenden, um die Funktionalität der Task und Task<TResult> Klassen 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 keine Run oder die System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult>oder System.Threading.Tasks.TaskCompletionSource<TResult> Klassen verwenden, um Instanzen Ihres benutzerdefinierten Aufgabentyps zu erstellen. Sie können sie nicht verwenden, da diese Klassen nur Task und Task<TResult> Objekte erstellen. Darüber hinaus können Sie die Aufgabenfortsetzungsmechanismen, die von Task, Task<TResult>, TaskFactoryund TaskFactory<TResult> bereitgestellt werden, nicht verwenden, um Instanzen Ihres benutzerdefinierten Aufgabentyps zu erstellen. Sie können sie nicht verwenden, da diese Klassen auch nur Task und Task<TResult> Objekte erstellen.
Verwandte Abschnitte
Titel | Beschreibung |
---|---|
Verkettung von Aufgaben durch Verwendung von Continuation Tasks | Beschreibt, wie Fortsetzungen funktionieren. |
Angehängte und abgetrennte untergeordnete Aufgaben | Beschreibt den Unterschied zwischen angefügten und getrennten untergeordneten Aufgaben. |
Vorgangsabbruch | Beschreibt die integrierte Abbruchunterstützung des Task-Objekts. |
Ausnahmebehandlung | Beschreibt, wie Ausnahmen für gleichzeitige Threads behandelt werden. |
Anleitung: Verwenden Sie Parallel.Invoke, um parallele Vorgänge auszuführen | Beschreibt, wie Invokeverwendet wird. |
So geben Sie einen Wert aus einem Task zurück | Beschreibt, wie Werte aus Aufgaben zurückgegeben werden. |
So geht"s: Abbrechen eines Tasks und seiner untergeordneten Elemente | Beschreibt, wie Aufgaben storniert werden. |
Anleitung: Erstellen von vorberechneten Tasks | Beschreibt, wie die Task.FromResult-Methode verwendet wird, um die Ergebnisse asynchroner Downloadvorgänge abzurufen, die in einem Cache gespeichert sind. |
Anleitung: Einen Binärbaum mit parallelen Aufgaben durchlaufen | Beschreibt, wie Aufgaben zum Traversieren einer binären Struktur verwendet werden. |
So geht"s: Einen eingebetteten Task entpacken | Veranschaulicht die Verwendung der Unwrap Erweiterungsmethode. |
Datenparallelität | Beschreibt, wie Sie mithilfe von For und ForEach parallele Schleifen für Daten erstellen. |
parallele Programmierung | Knoten der obersten Ebene für die parallele .NET Framework-Programmierung. |