.NET Framework 4 Task Parallelism (Task Parallel Library) Updated: March 2011 The Task Parallel Library (TPL), as its name implies, is based on the concept of the task. The term task parallelism refers to one or more independent tasks running concurrently. A task represents an asynchronous operation, and in some ways it resembles the creation of a new thread or ThreadPool work item, but at a higher level of abstraction. Tasks provide two primary benefits: More efficient and more scalable use of system resources. Behind the scenes, tasks are queued to the ThreadPool, which has been enhanced with algorithms (like hill-climbing) that determine and adjust to the number of threads that maximizes throughput. This makes tasks relatively lightweight, and you can create many of them to enable fine-grained parallelism. To complement this, widely-known work-stealing algorithms are employed to provide load-balancing. More programmatic control than is possible with a thread or work item. Tasks and the framework built around them provide a rich set of APIs that support waiting, cancellation, continuations, robust exception handling, detailed status, custom scheduling, and more.
For both of these reasons, in the .NET Framework 4, tasks are the preferred API for writing multi-threaded, asynchronous, and parallel code.

Creating and Running Tasks Implicitly
The Parallel..::.Invoke method provides a convenient way to run any number of arbitrary statements concurrently. Just pass in an Action delegate for each item of work. The easiest way to create these delegates is to use lambda expressions. The lambda expression can either call a named method, or provide the code inline. The following example shows a basic Invoke call that creates and starts two tasks that run concurrently. Note |
|---|
This documentation uses lambda expressions to define delegates in TPL. If you are not familiar with lambda expressions in C# or Visual Basic, see Lambda Expressions in PLINQ and TPL. |
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())
Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
Note |
|---|
The number of Task instances that are created behind the scenes by Invoke is not necessarily equal to the number of delegates that are provided. The TPL may employ various optimizations, especially with large numbers of delegates. |
For more information, see How to: Use Parallel.Invoke to Execute Parallel Operations. For greater control over task execution, or to return a value from the task, you have to work with Task objects more explicitly.

Creating and Running Tasks Explicitly
A task is represented by the System.Threading.Tasks..::.Task class. A task that returns a value is represented by the System.Threading.Tasks..::.Task<(Of <(TResult>)>) class, which inherits from Task. The task object handles the infrastructure details, and provides methods and properties that are accessible from the calling thread throughout the lifetime of the task. For example, you can access the Status property of a task at any time to determine whether it has started running, ran to completion, was canceled, or has thrown an exception. The status is represented by a TaskStatus enumeration. When you create a task, you give it a user delegate that encapsulates the code that the task will execute. The delegate can be expressed as a named delegate, an anonymous method, or a lambda expression. Lambda expressions can contain a call to a named method, as shown in the following example.
' 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 joining thread.
Console.WriteLine("Hello from the joining thread.")
' Output:
' Hello from the joining thread.
' Hello from taskA.
// Create a task and supply a user delegate by using a lambda expression.
var taskA = new Task(() => Console.WriteLine("Hello from taskA."));
// Start the task.
taskA.Start();
// Output a message from the joining thread.
Console.WriteLine("Hello from the calling thread.");
/* Output:
* Hello from the joining thread.
* Hello from taskA.
*/
You can also use the StartNew method to create and start a task in one operation. This is the preferred way to create and start tasks if creation and scheduling do not have to be separated, as shown in the following example
' Better: Create and start the task in one operation.
Dim taskA = Task.Factory.StartNew(Sub() Console.WriteLine("Hello from taskA."))
' Output a message from the joining thread.
Console.WriteLine("Hello from the joining thread.")
// Create and start the task in one operation.
var taskA = Task.Factory.StartNew(() => Console.WriteLine("Hello from taskA."));
// Output a message from the joining thread.
Console.WriteLine("Hello from the joining thread.");
Task exposes a static Factory property that returns a default instance of TaskFactory, so that you can call the method as Task.Factory.StartNew(…). Also, in this example, because the tasks are of type System.Threading.Tasks..::.Task<(Of <(TResult>)>), they each have a public Result property that contains the result of the computation. The tasks run asynchronously and may complete in any order. If Result is accessed before the computation completes, the property will block the thread until the value is available.
Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation1()),
Task(Of Double).Factory.StartNew(Function() DoComputation2()),
Task(Of Double).Factory.StartNew(Function() DoComputation3())}
Dim results() As Double
ReDim results(taskArray.Length)
For i As Integer = 0 To taskArray.Length
results(i) = taskArray(i).Result
Next
Task<double>[] taskArray = new Task<double>[]
{
Task<double>.Factory.StartNew(() => DoComputation1()),
// May be written more conveniently like this:
Task.Factory.StartNew(() => DoComputation2()),
Task.Factory.StartNew(() => DoComputation3())
};
double[] results = new double[taskArray.Length];
for (int i = 0; i < taskArray.Length; i++)
results[i] = taskArray[i].Result;
For more information, see How to: Return a Value from a Task. When you use a lambda expression to create a task's delegate, you have access to all the variables that are visible at that point in your source code. However, in some cases, most notably within loops, a lambda doesn't capture the variable as you might expect. It only captures the final value, not the value as it mutates after each iteration. You can access the value on each iteration by providing a state object to a task through its constructor, as shown in the following example:
Class MyCustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class
Sub TaskDemo2()
' Create the task object by using an Action(Of Object) to pass in custom data
' in the Task constructor. This is useful when you need to capture outer variables
' from within a loop.
' As an experiement, try modifying this code to capture i directly in the lamda,
' and compare results.
Dim taskArray() As Task
ReDim taskArray(10)
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = New Task(Sub(obj As Object)
Dim mydata = CType(obj, MyCustomData)
mydata.ThreadNum = Thread.CurrentThread.ManagedThreadId
Console.WriteLine("Hello from Task #{0} created at {1} running on thread #{2}.",
mydata.Name, mydata.CreationTime, mydata.ThreadNum)
End Sub,
New MyCustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks}
)
taskArray(i).Start()
Next
End Sub
class MyCustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}
void TaskDemo2()
{
// Create the task object by using an Action(Of Object) to pass in custom data
// in the Task constructor. This is useful when you need to capture outer variables
// from within a loop. As an experiement, try modifying this code to
// capture i directly in the lambda, and compare results.
Task[] taskArray = new Task[10];
for(int i = 0; i < taskArray.Length; i++)
{
taskArray[i] = new Task((obj) =>
{
MyCustomData mydata = (MyCustomData) obj;
mydata.ThreadNum = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Hello from Task #{0} created at {1} running on thread #{2}.",
mydata.Name, mydata.CreationTime, mydata.ThreadNum)
},
new MyCustomData () {Name = i, CreationTime = DateTime.Now.Ticks}
);
taskArray[i].Start();
}
}
This state is passed as an argument to the task delegate, and it is accessible from the task object by using the AsyncState property. Also, passing in data through the constructor might provide a small performance benefit in some scenarios.

Task ID
Every task receives an integer ID that uniquely identifies it in an application domain and that is accessible by using the Id property. The ID is useful for viewing task information in the Visual Studio debugger Parallel Stacks and Parallel Tasks windows. The ID is lazily created, which means that it isn't created until it is requested; therefore a task may have a different ID each time the program is run. For more information about viewing Task IDs in the debugger, see Using the Parallel Stacks Window.

Task Creation Options
Most APIs that create tasks provide overloads that accept a TaskCreationOptions parameter. By specifying one of these options, you instruct the task scheduler as to how to schedule the task on the thread pool. The following table lists the various task creation options. Element | Description |
|---|
None | The default option when no option is specified. The scheduler uses its default heuristics to schedule the task. | PreferFairness | Specifies that the task should be scheduled so that tasks created sooner will be more likely to be executed sooner, and tasks created later will be more likely to execute later. | LongRunning | Specifies that the task represents a long-running operation.. | AttachedToParent | Specifies that a task should be created as an attached child of the current Task, if one exists. For more information, see Nested Tasks and Child Tasks. |
The options may be combined by using a bitwise OR operation. The following example shows a task that has the LongRunning and PreferFairness option.
Dim task3 = New Task(Sub() MyLongRunningMethod(),
TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()
var task3 = new Task(() => MyLongRunningMethod(),
TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Creating Task Continuations
The Task..::.ContinueWith method and Task<(Of <(TResult>)>)..::.ContinueWith method let you specify a task to be started when the antecedent task completes. The continuation task's delegate is passed a reference to the antecedent, so that it can examine its status. In addition, a user-defined value can be passed from the antecedent to its continuation in the Result property, so that the output of the antecedent can serve as input for the continuation. In the following example, getData is started by the program code, then analyzeData is started automatically when getData completes, and reportData is started when analyzeData completes. getData produces as its result a byte array, which is passed into analyzeData. analyzeData processes that array and returns a result whose type is inferred from the return type of the Analyze method. reportData takes the input from analyzeData, and produces a result whose type is inferred in a similar manner and which is made available to the program in the Result property.
Dim getData As Task(Of Byte()) = New Task(Of Byte())(Function() GetFileData())
Dim analyzeData As Task(Of Double()) = getData.ContinueWith(Function(x) Analyze(x.Result))
Dim reportData As Task(Of String) = analyzeData.ContinueWith(Function(y As Task(Of Double)) Summarize(y.Result))
getData.Start()
System.IO.File.WriteAllText("C:\reportFolder\report.txt", reportData.Result)
Task<byte[]> getData = new Task<byte[]>(() => GetFileData());
Task<double[]> analyzeData = getData.ContinueWith(x => Analyze(x.Result));
Task<string> reportData = analyzeData.ContinueWith(y => Summarize(y.Result));
getData.Start();
//or...
Task<string> reportData2 = Task.Factory.StartNew(() => GetFileData())
.ContinueWith((x) => Analyze(x.Result))
.ContinueWith((y) => Summarize(y.Result));
System.IO.File.WriteAllText(@"C:\reportFolder\report.txt", reportData.Result);
The ContinueWhenAll and ContinueWhenAny methods enable you to continue from multiple tasks. For more information, see Continuation Tasks and How to: Chain Multiple Tasks with Continuations.

Creating Detached Nested Tasks
When user code that is running in a task creates a new task and does not specify the AttachedToParent option, the new task not synchronized with the outer task in any special way. Such tasks are called a detached nested task. The following example shows a task that creates one detached nested task.
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.")
' Output:
' Outer task beginning.
' Outer task completed.
' Detached child completed.
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.");
/* Output:
Outer task beginning.
Outer task completed.
Detached task completed.
*/
Note that the outer task does not wait for the nested task to complete.

Creating Child Tasks
When user code that is running in a task creates a task with the AttachedToParent option, the new task is known as a child task of the originating task, which is known as the parent task. You can use the AttachedToParent option to express structured task parallelism, because the parent task implicitly waits for all child tasks to complete. The following example shows a task that creates one child task:
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Parent task beginning.")
Dim child = Task.Factory.StartNew(Sub()
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completed.")
End Sub,
TaskCreationOptions.AttachedToParent)
End Sub)
outer.Wait()
Console.WriteLine("Parent task completed.")
' Output:
' Parent task beginning.
' Attached child completed.
' Parent task completed.
var parent = Task.Factory.StartNew(() =>
{
Console.WriteLine("Parent task beginning.");
var child = Task.Factory.StartNew(() =>
{
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completed.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent task completed.");
/* Output:
Parent task beginning.
Attached task completed.
Parent task completed.
*/
For more information, see Nested Tasks and Child Tasks.

Waiting on Tasks
The System.Threading.Tasks..::.Task type and System.Threading.Tasks..::.Task<(Of <(TResult>)>) type provide several overloads of a Task..::.Wait and Task<(Of <(TResult>)>)..::.Wait method that enable you to wait for a task to complete. In addition, overloads of the static Task..::.WaitAll method and Task..::.WaitAny method let you wait for any or all of an array of tasks to complete. Typically, you would wait for a task for one of these reasons: The following example shows the basic pattern that does not involve exception handling.
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...
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...
For an example that shows exception handling, see How to: Handle Exceptions Thrown by Tasks. Some overloads let you specify a timeout, and others take an additional CancellationToken as an input parameter, so that the wait itself can be canceled either programmatically or in response to user input. When you wait on a task, you implicitly wait on all children of that task that were created by using the TaskCreationOptions AttachedToParent option. Task..::.Wait returns immediately if the task has already completed. Any exceptions raised by a task will be thrown by a Wait method, even if the Wait method was called after the task completed. For more information, see How to: Wait on One or More Tasks to Complete.

Handling Exceptions in Tasks
When a task throws one or more exceptions, the exceptions are wrapped in a AggregateException. That exception is propagated back to the thread that joins with the task, which is typically the thread that is waiting on the task or attempts to access the task's Result property. This behavior serves to enforce the .NET Framework policy that all unhandled exceptions by default should tear down the process. The calling code can handle the exceptions by using the Wait, WaitAll, or WaitAny method or the Result()()() property on the task or group of tasks, and enclosing the Wait method in a try-catch block. The joining thread can also handle exceptions by accessing the Exception property before the task is garbage-collected. By accessing this property, you prevent the unhandled exception from triggering the exception propagation behavior which tears down the process when the object is finalized. For more information about exceptions and tasks, see Exception Handling (Task Parallel Library) and How to: Handle Exceptions Thrown by Tasks.

Canceling Tasks

The TaskFactory Class

Tasks Without Delegates
In some cases, you may want to use a Task to encapsulate some asynchronous operation that is performed by an external component instead of your own user delegate. If the operation is based on the Asynchronous Programming Model Begin/End pattern, you can use the FromAsync methods. If that is not the case, you can use the TaskCompletionSource<(Of <(TResult>)>) object to wrap the operation in a task and thereby gain some of the benefits of Task programmability, for example, support for exception propagation and continuations. For more information, see TaskCompletionSource<(Of <(TResult>)>).

Custom Schedulers
Most application or library developers do not care which processor the task runs on, how it synchronizes its work with other tasks, or how it is scheduled on the System.Threading..::.ThreadPool. They only require that it execute as efficiently as possible on the host computer. If you require more fine-grained control over the scheduling details, the Task Parallel Library lets you configure some settings on the default task scheduler, and even lets you supply a custom scheduler. For more information, see TaskScheduler.

Related Data Structures

Custom Task Types

Related Topics

See Also

Change History
Date | History | Reason |
|---|
March 2011
| Added information about how to inherit from the Task and Task<(Of <(TResult>)>) classes. |
Information enhancement.
|
|
.NET Framework 4 Aufgabenparallelität (Task Parallel Library) Die Task Parallel Library (TPL) basiert, wie der Name vermuten lässt, auf dem Konzept von Aufgaben. Der Begriff Aufgabenparallelität bezeichnet eine oder mehrere eigenständige Aufgaben, die gleichzeitig ausgeführt werden. Eine Aufgabe ist ein asynchroner Vorgang und ähnelt in gewissen Aspekten der Erstellung eines neuen Threads oder einer Arbeitsaufgabe im Threadpool, ist jedoch auf einer höheren Ebene der Abstraktion angesiedelt. Aufgaben bieten zwei Hauptvorteile: Effiziente und skalierbare Verwendung von Systemressourcen. Im Hintergrund werden Aufgaben in die Warteschlange des Threadpools eingereiht, der mit Algorithmen (z. B. Hill-Climbing) verbessert wurde, durch die die Anzahl von Threads bestimmt und angepasst und somit der Durchsatz maximiert wird. Aufgaben sind hierdurch relativ einfach strukturiert, und Sie können für eine differenzierte Parallelität viele Aufgaben erstellen. Als Ergänzung hierzu werden allgemeingültige Arbeitsübernahme-Algorithmen eingesetzt, um einen Lastenausgleich bereitzustellen. Stärker programmgesteuerte Kontrolle als bei Threads oder Arbeitsaufgaben. 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 sind Aufgaben in .NET Framework 4 die bevorzugte API zum Schreiben von asynchronem und parallelem Multithreadcode.

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 Lambda-Ausdrücke. Der Lambda-Ausdruck 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. Hinweis |
|---|
In dieser Dokumentation werden Delegaten in TPL mithilfe von Lambda-Ausdrücken definiert. Falls Sie nicht mit der Verwendung von Lambda-Ausdrücken in C# oder Visual Basic vertraut sind, finden Sie entsprechende Informationen unter Lambda-Ausdrücke in PLINQ und TPL. |
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())
Parallel.Invoke(() => DoSomeWork(), () => 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 Gewusst wie: Ausführen von parallelen Vorgängen mithilfe von Parallel.Invoke. Wenn Sie die Aufgabenausführung präziser steuern oder einen Wert in der Aufgabe zurückgeben möchten, müssen Sie expliziter mit Task-Objekten arbeiten.

Explizites Erstellen und Ausführen von Aufgaben
Eine Aufgabe wird durch die System.Threading.Tasks..::.Task-Klasse dargestellt. Eine Aufgabe, die einen Wert zurückgibt, wird durch die System.Threading.Tasks..::.Task<(Of <(TResult>)>)-Klasse dargestellt, die von Task erbt. Das Task-Objekt 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. Lambda-Ausdrücke können einen Aufruf einer benannten Methode enthalten, wie im folgenden Beispiel gezeigt.
' 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 joining thread.
Console.WriteLine("Hello from the joining thread.")
' Output:
' Hello from the joining thread.
' Hello from taskA.
// Create a task and supply a user delegate by using a lambda expression.
var taskA = new Task(() => Console.WriteLine("Hello from taskA."));
// Start the task.
taskA.Start();
// Output a message from the joining thread.
Console.WriteLine("Hello from the calling thread.");
/* Output:
* Hello from the joining thread.
* Hello from taskA.
*/
Sie können auch die StartNew-Methode verwenden, um eine Aufgabe in einem Schritt zu erstellen und zu starten. Dies ist die bevorzugte Vorgehensweise zum Erstellen und Starten von Aufgaben, wenn Erstellung und Planung nicht getrennt werden müssen, wie im folgenden Beispiel gezeigt.
' Better: Create and start the task in one operation.
Dim taskA = Task.Factory.StartNew(Sub() Console.WriteLine("Hello from taskA."))
' Output a message from the joining thread.
Console.WriteLine("Hello from the joining thread.")
// Create and start the task in one operation.
var taskA = Task.Factory.StartNew(() => Console.WriteLine("Hello from taskA."));
// Output a message from the joining thread.
Console.WriteLine("Hello from the joining thread.");
Die Aufgabe verfügt über eine statische Factory-Eigenschaft, die eine Standardinstanz von TaskFactory zurückgibt, sodass Sie die Methode als Task.Factory.StartNew(…) aufrufen können. Da die Aufgaben in diesem Beispiel den Typ System.Threading.Tasks..::.Task<(Of <(TResult>)>) aufweisen, verfügen sie zudem über eine öffentliche 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 vor Abschluss der Berechnung auf Result zugegriffen wird, sperrt die Eigenschaft den Thread, bis der Wert verfügbar ist.
Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation1()),
Task(Of Double).Factory.StartNew(Function() DoComputation2()),
Task(Of Double).Factory.StartNew(Function() DoComputation3())}
Dim results() As Double
ReDim results(taskArray.Length)
For i As Integer = 0 To taskArray.Length
results(i) = taskArray(i).Result
Next
Task<double>[] taskArray = new Task<double>[]
{
Task<double>.Factory.StartNew(() => DoComputation1()),
// May be written more conveniently like this:
Task.Factory.StartNew(() => DoComputation2()),
Task.Factory.StartNew(() => DoComputation3())
};
double[] results = new double[taskArray.Length];
for (int i = 0; i < taskArray.Length; i++)
results[i] = taskArray[i].Result;
Weitere Informationen finden Sie unter Gewusst wie: Zurückgeben eines Werts aus einer Aufgabe. Wenn Sie einen Lambda-Ausdruck verwenden, um den Delegaten einer Aufgabe 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 möglicherweise erwartet vom Lambda-Ausdruck erfasst. Es wird nur der endgültige Wert erfasst, und nicht der nach jeder Iteration geänderte Wert. Sie können auf den Wert für jede Iteration zugreifen, indem Sie für die Aufgabe über ihren Konstruktor ein Zustandsobjekt bereitstellen, wie im folgenden Beispiel gezeigt:
Class MyCustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class
Sub TaskDemo2()
' Create the task object by using an Action(Of Object) to pass in custom data
' in the Task constructor. This is useful when you need to capture outer variables
' from within a loop.
' As an experiement, try modifying this code to capture i directly in the lamda,
' and compare results.
Dim taskArray() As Task
ReDim taskArray(10)
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = New Task(Sub(obj As Object)
Dim mydata = CType(obj, MyCustomData)
mydata.ThreadNum = Thread.CurrentThread.ManagedThreadId
Console.WriteLine("Hello from Task #{0} created at {1} running on thread #{2}.",
mydata.Name, mydata.CreationTime, mydata.ThreadNum)
End Sub,
New MyCustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks}
)
taskArray(i).Start()
Next
End Sub
class MyCustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}
void TaskDemo2()
{
// Create the task object by using an Action(Of Object) to pass in custom data
// in the Task constructor. This is useful when you need to capture outer variables
// from within a loop. As an experiement, try modifying this code to
// capture i directly in the lambda, and compare results.
Task[] taskArray = new Task[10];
for(int i = 0; i < taskArray.Length; i++)
{
taskArray[i] = new Task((obj) =>
{
MyCustomData mydata = (MyCustomData) obj;
mydata.ThreadNum = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Hello from Task #{0} created at {1} running on thread #{2}.",
mydata.Name, mydata.CreationTime, mydata.ThreadNum)
},
new MyCustomData () {Name = i, CreationTime = DateTime.Now.Ticks}
);
taskArray[i].Start();
}
}
Dieser Zustand wird als Argument an den Aufgabendelegaten übergeben, und mit der AsyncState-Eigenschaft kann über das Aufgabenobjekt auf den Zustand zugegriffen werden. Außerdem bietet in einigen Szenarien das Übergeben von Daten über den Konstruktor möglicherweise eine etwas höhere Leistung.

Aufgaben-ID
Jeder Aufgabe wird eine ganzzahlige ID zugeordnet, durch die diese in einer Anwendungsdomäne eindeutig identifiziert wird und auf die mit der 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 haben. Weitere Informationen zum Anzeigen von Aufgaben-IDs im Debugger finden Sie unter Verwenden des Fensters "Parallele Stapel".

Aufgabenerstellungsoptionen
Die meisten APIs, die Aufgaben erstellen, stellen Überladungen bereit, die einen TaskCreationOptions-Parameter akzeptieren. Indem Sie eine dieser Optionen angeben, weisen Sie den Taskplaner an, wie die Aufgabe im Threadpool geplant werden soll. In der folgenden Tabelle sind die verschiedenen Aufgabenerstellungsoptionen aufgeführt. Element | Beschreibung |
|---|
None | Dies ist die Standardoption, wenn keine Option angegeben wurde. Der Planer verwendet zum Planen der Aufgabe seine Standardheuristik. | PreferFairness | Gibt an, dass die Aufgabe so geplant werden soll, dass früher erstellte Aufgaben mit großer Wahrscheinlichkeit auch früher ausgeführt werden als Aufgaben, die später erstellt wurden. | LongRunning | Gibt an, dass die Aufgabe ein Vorgang mit langer Laufzeit darstellt. | AttachedToParent | Gibt an, dass eine Aufgabe als angefügtes untergeordnetes Element der aktuellen Aufgabe erstellt werden soll (sofern vorhanden). Weitere Informationen finden Sie unter Geschachtelte Aufgaben und untergeordnete Aufgaben. |
Die Optionen können mit einer bitweisen OR-Operation kombiniert werden. Im folgenden Beispiel wird eine Aufgabe veranschaulicht, die über die LongRunning-Option und die PreferFairness-Option verfügt.
Dim task3 = New Task(Sub() MyLongRunningMethod(),
TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()
var task3 = new Task(() => MyLongRunningMethod(),
TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Erstellen von Aufgabenfortsetzungen
Mithilfe der Task..::.ContinueWith-Methode und Task<(Of <(TResult>)>)..::.ContinueWith-Methode können Sie angeben, dass eine Aufgabe gestartet werden soll, wenn die Vorgängeraufgabe abgeschlossen wurde. An den Delegaten der Fortsetzungsaufgabe wird ein Verweis auf den Vorgänger übergeben, damit er dessen Status überprüfen kann. Zudem kann ein benutzerdefinierter Wert in der Result-Eigenschaft vom Vorgänger an die Fortsetzung übergeben werden, sodass die Ausgabe des Vorgängers als Eingabe für die Fortsetzung verwendet werden kann. Im folgenden Beispiel wird getData vom Programmcode gestartet. Anschließend wird analyzeData automatisch gestartet, wenn getData abgeschlossen wurde, und reportData wird gestartet, wenn analyzeData abgeschlossen wurde. getData erzeugt als Ergebnis ein Bytearray, das an analyzeData übergeben wird. analyzeData verarbeitet dieses Array und gibt ein Ergebnis zurück, dessen Typ vom Rückgabetyp der Analyze-Methode abgeleitet wird. reportData übernimmt die Eingabe von analyzeData und erzeugt ein Ergebnis, dessen Typ auf eine ähnliche Weise abgeleitet wird und das dem Programm in der Result-Eigenschaft zur Verfügung gestellt wird.
Dim getData As Task(Of Byte()) = New Task(Of Byte())(Function() GetFileData())
Dim analyzeData As Task(Of Double()) = getData.ContinueWith(Function(x) Analyze(x.Result))
Dim reportData As Task(Of String) = analyzeData.ContinueWith(Function(y As Task(Of Double)) Summarize(y.Result))
getData.Start()
System.IO.File.WriteAllText("C:\reportFolder\report.txt", reportData.Result)
Task<byte[]> getData = new Task<byte[]>(() => GetFileData());
Task<double[]> analyzeData = getData.ContinueWith(x => Analyze(x.Result));
Task<string> reportData = analyzeData.ContinueWith(y => Summarize(y.Result));
getData.Start();
//or...
Task<string> reportData2 = Task.Factory.StartNew(() => GetFileData())
.ContinueWith((x) => Analyze(x.Result))
.ContinueWith((y) => Summarize(y.Result));
System.IO.File.WriteAllText(@"C:\reportFolder\report.txt", reportData.Result);
Die ContinueWhenAll-Methode und die ContinueWhenAny-Methode ermöglichen es Ihnen, die Ausführung von mehreren Aufgaben fortzusetzen. Weitere Informationen finden Sie unter Fortsetzungsaufgaben und Gewusst wie: Verketten von mehreren Aufgaben mit Fortsetzungen.

Erstellen von getrennten geschachtelten Aufgaben
Wenn durch Benutzercode, der in einer Aufgabe ausgeführt wird, eine neue Aufgabe erstellt wird und die AttachedToParent-Option nicht angegeben ist, wird die neue Aufgabe auf keine besondere Weise mit der äußeren Aufgabe synchronisiert. Solche Aufgaben werden als getrennte geschachtelte Aufgabe bezeichnet. Im folgenden Beispiel wird eine Aufgabe dargestellt, die eine getrennte geschachtelte Aufgabe erstellt.
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.")
' Output:
' Outer task beginning.
' Outer task completed.
' Detached child completed.
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.");
/* Output:
Outer task beginning.
Outer task completed.
Detached task completed.
*/
Beachten Sie, dass die äußere Aufgabe nicht wartet, bis die geschachtelte Aufgabe abgeschlossen wurde.

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 eine untergeordnete Aufgabe der ursprünglichen Aufgabe bezeichnet, die als übergeordnete Aufgabe bezeichnet wird. Mithilfe der AttachedToParent-Option können Sie eine strukturierte Aufgabenparallelität angeben, da die übergeordnete Aufgabe implizit auf den Abschluss aller untergeordneten Aufgaben wartet. Im folgenden Beispiel wird eine Aufgabe dargestellt, die eine untergeordnete Aufgabe erstellt:
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Parent task beginning.")
Dim child = Task.Factory.StartNew(Sub()
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completed.")
End Sub,
TaskCreationOptions.AttachedToParent)
End Sub)
outer.Wait()
Console.WriteLine("Parent task completed.")
' Output:
' Parent task beginning.
' Attached child completed.
' Parent task completed.
var parent = Task.Factory.StartNew(() =>
{
Console.WriteLine("Parent task beginning.");
var child = Task.Factory.StartNew(() =>
{
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completed.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent task completed.");
/* Output:
Parent task beginning.
Attached task completed.
Parent task completed.
*/
Weitere Informationen finden Sie unter Geschachtelte Aufgaben und untergeordnete Aufgaben.

Warten auf Aufgaben
Der System.Threading.Tasks..::.Task-Typ und der System.Threading.Tasks..::.Task<(Of <(TResult>)>)-Typ bieten mehrere Überladungen der Task..::.Wait-Methode bzw. der Task<(Of <(TResult>)>)..::.Wait-Methode, die Ihnen das Warten auf den Abschluss einer Aufgabe ermöglichen. Außerdem können Sie mittels Überladungen der statischen Task..::.WaitAll-Methode und Task..::.WaitAny-Methode Sie 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.
Im folgenden Beispiel wird das grundlegende Muster dargestellt, das keine Ausnahmebehandlung beinhaltet.
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...
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...
Ein Beispiel, das die Ausnahmebehandlung veranschaulicht, finden Sie unter Gewusst wie: Behandeln von Ausnahmen, die von Aufgaben ausgelöst werden. 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 in Folge einer Benutzereingabe abgebrochen werden kann. Wenn Sie auf eine Aufgabe warten, warten Sie implizit auf alle untergeordneten Elemente dieser Aufgabe, 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 Wait-Methode ausgelöst, auch wenn die Wait-Methode nach Abschluss der Aufgabe aufgerufen wurde. Weitere Informationen finden Sie unter Gewusst wie: Warten bis zum Abschluss einer oder mehrerer Aufgaben.

Behandeln von Ausnahmen in Aufgaben
Wenn eine Aufgabe eine oder mehrere Ausnahmen auslöst, werden die Ausnahmen in eine AggregateException eingeschlossen. Diese Ausnahme wird an den Thread zurückgegeben, der mit der Aufgabe verbunden ist. In der Regel ist dies der Thread, der auf die Aufgabe wartet oder versucht, auf die Result-Eigenschaft der Aufgabe zuzugreifen. Dieses Verhalten trägt dazu bei, dass die .NET Framework-Richtlinie umgesetzt wird, derzufolge alle nicht behandelten Ausnahmen standardmäßig zu einem Abbruch des Prozesses führen. Der aufrufende Code kann die Ausnahmen mit der Methode Wait, WaitAll oder WaitAny oder mit der Result()()()-Eigenschaft in der Aufgabe bzw. Aufgabengruppe und durch Einschließen der Wait-Methode in einen try/catch-Block behandeln. 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 nicht behandelte Ausnahme das Ausnahmeweitergabeverhalten auslöst, durch das der Prozess beim Abschluss des Objekts abgebrochen wird. Weitere Informationen zu Ausnahmen und Aufgaben finden Sie unter Ausnahmebehandlung (Task Parallel Library) und Gewusst wie: Behandeln von Ausnahmen, die von Aufgaben ausgelöst werden.

Abbrechen von Aufgaben

Die TaskFactory-Klasse
Die TaskFactory-Klasse stellt statische Methoden bereit, die einige allgemeine Muster zum Erstellen und Starten von Aufgaben und Fortsetzungsaufgaben kapseln. Die standardmäßige TaskFactory ist als statische Eigenschaft in der Task-Klasse oder Task<(Of <(TResult>)>)-Klasse verfügbar. 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 die Aufgabe 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<(Of <(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<(Of <(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 Standardtaskplaner konfigurieren und sogar einen benutzerdefinierten Planer angeben. Weitere Informationen finden Sie unter TaskScheduler.

Verwandte Datenstrukturen

Benutzerdefinierte Aufgabentypen
Es wird empfohlen, nicht von System.Threading.Tasks..::.Task oder System.Threading.Tasks..::.Task<(Of <(TResult>)>) zu erben. Verwenden Sie stattdessen die AsyncState-Eigenschaft, um einem Task-Objekt oder einem Task<(Of <(TResult>)>)-Objekt zusätzliche Daten oder einen zusätzlichen Zustand zuzuordnen. Sie können auch Erweiterungsmethoden verwenden, um die Funktionen der Task<(Of <(TResult>)>)-Klasse und der Task-Klasse zu erweitern. Weitere Informationen über Erweiterungsmethoden finden Sie unter Erweiterungsmethoden (C#-Programmierhandbuch) und Erweiterungsmethoden (Visual Basic). Wenn Sie von Task oder Task<(Of <(TResult>)>) erben müssen, können Sie nicht mit der Klasse System.Threading.Tasks..::.TaskFactory, System.Threading.Tasks..::.TaskFactory<(Of <(TResult>)>) oder System.Threading.Tasks..::.TaskCompletionSource<(Of <(TResult>)>) Instanzen des benutzerdefinierten Aufgabentyps erstellen, da diese Klassen nur Task-Objekte und Task<(Of <(TResult>)>)-Objekte erstellen. Außerdem können Sie nicht die von Task, Task<(Of <(TResult>)>), TaskFactory und TaskFactory<(Of <(TResult>)>) bereitgestellten Aufgabenfortsetzungsmechanismen verwenden, um Instanzen des benutzerdefinierten Aufgabentyps zu erstellen, da mit diesen Mechanismen ebenfalls nur Task-Objekte und Task<(Of <(TResult>)>)-Objekte erstellt werden.

Verwandte Themen

Siehe auch

Änderungsprotokoll
Datum | Versionsgeschichte | Grund |
|---|
März 2011
| Informationen über das Erben von der Task<(Of <(TResult>)>)-Klasse und der Task-Klasse hinzugefügt. |
Informationsergänzung.
|
|