Introduction to Tasks

[This documentation is for preview only, and is subject to change in later releases. Blank topics are included as placeholders.]

The Task Parallel Library (TPL), as its name implies, is based on the concept of the task. 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 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 version 4, tasks are the preferred API for writing multi-threaded, asynchronous, and parallel code.

Creating and Running Tasks

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<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.

        ' Output a message from the calling thread.
        Console.WriteLine("Hello from the calling thread.")

        ' Output:
        ' Hello from the calling 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.

            // Output a message from the calling thread.
            Console.WriteLine("Hello from the calling thread.");

            /* Output:
             * Hello from the calling 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. 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 Task<double>, 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 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
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.

Also, you can optionally provide a state object to a task, as shown in the following example:

    Class MyCustomData

        Public MyDate As DateTime
        Public Name As String
    End Class

    Shared Sub TaskDemo2()
        ' Create the task object by using an Action(object) to pass in custom data.
        Dim taskB As Task(Of Action(Of Object))
        taskB = New Task(Sub(obj As Object)
                             Dim mydata = CType(obj, MyCustomData)
                             Console.WriteLine("Hello from {0}. Today is {1}.", mydata.Name, mydata.MyDate)
                         End Sub,
        New MyCustomData With {.Name = "taskB", .MyDate = DateTime.Today}

        ' Start the task.
    End Sub

    ' Sample Output:
    ' Hello from taskB. Today is 8/6/2009 12:00:00 AM.

        class MyCustomData
            public DateTime Date;
            public String Name;

        static void TaskDemo2()
            // Create the task object by using an Action<object> to pass in custom data.
            var taskB = Task.Factory.StartNew((obj) =>
                    MyCustomData data = (MyCustomData)obj;
                    Console.WriteLine("Hello from {0}. Today is {1}.", data.Name, data.Date);
                new MyCustomData { Name = "taskB", Date = DateTime.Today });

            // Or use the dynamic keyword to avoid having to 
            // create a named class to hold the data:
            var taskC = Task.Factory.StartNew(state =>
                dynamic data = state;
            }, new { Name = "taskB", Date = DateTime.Today });


        // Sample Output:
        // Hello from taskB. Today is 8/6/2009 12:00:00 AM.

This state is passed as an argument to the task delegate, and it is viewable on the task object by using the AsyncState property.

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 and 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.




The default option when no option is specified. The scheduler uses its default heuristics to schedule the task.


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.


Specifies that the task represents a long-running operation..


Specifies that a task should be created as an attached child of the current Task, if one exists. For more information, see Child Tasks.

The options may be combined with 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)
var task3 = new Task(() => MyLongRunningMethod(),
                    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);

Creating Task Continuations

The TaskContinueWith() method and TaskContinueWith() 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))


        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));


            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() method and ContinueWhenAny() method let you 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 tasks. 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()
                                                                            Console.WriteLine("Detached task completed.")
                                                                        End Sub)
                                  End Sub)
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(() =>
                    Console.WriteLine("Detached task completed.");


            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()
                                                                             Console.WriteLine("Attached child completed.")
                                                                         End Sub,

                                   End Sub)
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(() =>
        Console.WriteLine("Attached child completed.");
    }, TaskCreationOptions.AttachedToParent);


Console.WriteLine("Parent task completed.");

/* Output:
    Parent task beginning.
    Attached task completed.
    Parent task completed.

For more information, see Child Tasks.

Waiting on Tasks

The System.Threading.Tasks.Task type and System.Threading.Tasks.Task<TResult> type provide several overloads of a Wait method that let you wait for a task to complete. In addition, overloads of the static TaskWaitAll() method and TaskWaitAny() 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 main thread depends on the final result computed by a task.

  • You have to handle exceptions that might be thrown from the task.

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())

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


For an example that shows exception handling, see the following section.

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. 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 Task class supports cooperative cancellation and is fully integrated with the System.Threading.CancellationTokenSource class and the System.Threading.CancellationToken class, which are new in the .NET Framework version 4. Many of the constructors in the System.Threading.Tasks.Task class take a CancellationToken as an input parameter. Many of the StartNew() overloads also take a CancellationToken.

You can create the token, and issue the cancellation request at some later time, by using the CancellationTokenSource class. Pass the token to the Task as an argument, and also reference the same token in your user delegate, which does the work of responding to a cancellation request. For more information, see Task Cancellation and How to: Cancel a Task and Its Children.

The TaskFactory Class

The TaskFactory class provides static methods that encapsulate some common patterns for creating and starting tasks and continuation tasks.

  • The most common pattern is StartNew, which creates and starts a task in one statement. For more information, see StartNew().

  • When you create continuation tasks from multiple antecedents, use the ContinueWhenAll method or ContinueWhenAny method. For more information, see Continuation Tasks.

  • To encapsulate Asynchronous Programming Model BeginX and EndX methods in a Task or Task<TResult> instance, use the FromAsync() methods. For more information, see TPL and Traditional .NET Asynchronous Programming.

The default TaskFactory is accessible as a static property on the Task class or Task<TResult> class. You can also instantiate a TaskFactory directly and specify various options that include a CancellationToken, a TaskCreationOptions option, a TaskContinuationOptions option, or a TaskScheduler. Whatever options are specified when you create the task factory will be applied to all tasks that it creates, unless the Task is created by using the TaskCreationOptions enumeration, in which case the task's options override those of the task factory.

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 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.

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 onto 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.

The TPL has several new public types that are useful in both parallel and sequential scenarios. These include several thread-safe, fast and scalable collection classes in the System.Collections.Concurrent namespace, and several new synchronization types, for example, SemaphoreLock and System.Threading.ManualResetEventSlim, which are more efficient than their predecessors for specific kinds of workloads. Other new types in the .NET Framework version 4, for example, System.Threading.Barrier and System.Threading.SpinLock, provide functionality that was not available in earlier releases. For more information, see Data Structures for Parallel Programming.

See Also


Task Parallel Library Overview