Advanced Task Parallel Library Continuations

.NET Parallel Programming involves more than parallel workloads and concurrency safe data structures. Task Parallel Library (TPL) also features patterns and data structures for ordering and arranging a workload's execution. TPL Continuations play a big part in ordering and arranging a parallel workload. TPL encapsulates a workload and the result of running the workload in a Task. Continuations spawn new Tasks in response to the completion of a prior Task. A prior article demonstrated how to make a Task's execution dependent on the success or failure of another Task. The paragraphs below will take this concept a step further, demonstrating how to structure Task execution with multiple Task dependencies.


An introduction to Continuations is beyond the scope of this article, but a short introduction can be found here Sample Continuation code appears below.

           Task.ContinueWith((t) =>
                Console.WriteLine("This continuation also ran.");

As stated earlier, Continuations are Tasks that execute in response to the result of another Task. The prior completed Task is often called an Antecedent. Continuations can execute when a Task completes or, for example, when a Task Faults. Like other running Tasks, Continuations can be LongRunning and can be configured to execute on the same Thread as the Antecedent Task.

Continuations can also have multiple Antecedents, but to configure multiple Antecedents a developer needs the Factory Property on the Task class.

Task Factory Property

Factory is a static property on the Task class. The Factory property is a TaskFactory class. Some methods on the TaskFactory class appear below.

public Task ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Action<Task<TAntecedentResult>[]> continuationAction);
public Task ContinueWhenAll(Task[] tasks, Action<Task[]> continuationAction);
public Task ContinueWhenAll(Task[] tasks, Action<Task[]> continuationAction, TaskContinuationOptions continuationOptions);
public Task ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Action<Task<TAntecedentResult>> continuationAction, CancellationToken cancellationToken);
public Task<TResult> StartNew<TResult>(Func<TResult> function);
public Task<TResult> StartNew<TResult>(Func<object, TResult> function, object state);

StartNew creates and starts a new Task. TaskFactory is aptly named. Observe how the methods above all return a Task class. As stated earlier Continuations are Tasks that execute in response to the result of an Antecedent Task. Tasks with multiple Antecedents are created from ContinueWhenAny and ContinueWhenAll methods. Both methods and related overloads accept an array of Tasks. For example: ContinueWhenAll creates a Task when an array of Tasks completes.

ContinueWhenAny and ContinueWhenAll both leverage other parts of TPL like Cancellations and TaskCreationOptions. Like other Continuations, Tasks created with ContinueWhenAll and ContinueWhenAny can create a LongRunning or ParentTask.

The remainder of this article will demonstrate how to put the ContinueWhenAll to work.

Array of Tasks and TaskCompletionSource

The sample code below creates an array of Tasks each configured for a Llamda payload.

            var antecedents = new Task[3]
            { new Task (() =>
                    SpinWait.SpinUntil(()=>{return false;},1000);
                    Console.WriteLine("Completed 1");
                ,new Task(() =>
                    SpinWait.SpinUntil(()=>{return false;},2000);
                    Console.WriteLine("Completed 2");
                ,new Task(() =>
                    SpinWait.SpinUntil(()=>{return false;},3000);
                    Console.WriteLine("Completed 3");

Continuation Antecedents are not limited to Tasks. Tasks store a work payload and the result of the executed payload. Tasks also maintain the status of the running payload and are the core TPL component. Not all Tasks, however, have an Action or Func<T> payload. Some Tasks can be manipulated using the TaskCompletionSource. Below are some methods and properties of the TaskCompletionSource.

    public class TaskCompletionSource<TResult>
        public TaskCompletionSource();

        public void SetCanceled();
        public void SetException(Exception exception);
        public void SetResult(TResult result);
        public bool TrySetCanceled();
        public bool TrySetException(Exception exception);
        public bool TrySetResult(TResult result);

Methods prefixed with "Set" transition the underlying Task to the appropriate state and generate an Exception if the Task is already in a completed state. Methods prefixed with "Try" attempt to transition state, but return false if the Task is in a completed state. TaskCompletionSource allows a developer to, for example, join code to a Continuation without directly creating and starting a Task. The sample code below demonstrates how to combine the TaskCompletionSource with the array of Tasks from the example above.

            var tcs = new TaskCompletionSource<object>();
            var fullList = new List<Task>();

The Task Property on TaskCompletionSource is the key. Task Property is a Task without an Action or Func<T> payload. "Set" and "Try" method on the TaskCompletionSource transition the Task to the appropriate state. In the sample, ContinueWhenAll creates a Task when the array of Tasks complete and the Task behind the TaskCompletionSource transitions to a completed state.


A Sample ContinueWhenAll that accepts the array of Tasks and TaskCompletionSource Task appears below.

            Task.Factory.ContinueWhenAll(fullList.ToArray(), (tasks) =>
                Console.WriteLine("ContinueWhenAll started...");
                foreach (var t in tasks)
                    if (t.IsCanceled)
                    { Console.WriteLine("Antecedent was cancelled"); }
            foreach (var t in antecedents) { t.Start(); }
            SpinWait.SpinUntil(() => { return false; }, 5000);

One of the ContinueWhenAll parameter is an Action or Func<T> that accepts an array of Tasks. A Continuation is often dependent on the Results of its Antecedents. With multiple Antecedents the results are an array. Antecedents can fault or be cancelled so a Continuation can observe Exceptions or cancellations just like a Continuation attached to a single Task.

ContinueWhenAll doesn't create the Task until all Tasks have completed. In the example all Tasks quickly complete and after a short delay the TaskCompletionSource transitions its Task to the cancelled state.


More advanced Continuations with multiple Antecedents can be configured with the Task's Factory property. Continuations are not limited to simply Tasks. The TaskCompletionSource can also be a useful resource.


"Microsoft .NET Framework 4.0 Task Parallel Library Continuations"

About the Author

Jeffrey Juday

Jeff is a software developer specializing in enterprise application integration solutions utilizing BizTalk, SharePoint, WCF, WF, and SQL Server. Jeff has been developing software with Microsoft tools for more than 15 years in a variety of industries including: military, manufacturing, financial services, management consulting, and computer security. Jeff is a Microsoft BizTalk MVP. Jeff spends his spare time with his wife Sherrill and daughter Alexandra.

Related Articles


  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Not long ago, security was viewed as one of the biggest obstacles to widespread adoption of cloud-based deployments for enterprise software solutions. However, the combination of advancing technology and an increasing variety of threats that companies must guard against is rapidly turning the tide. Cloud vendors typically offer a much higher level of data center and virtual system security than most organizations can or will build out on their own. Read this white paper to learn the five ways that cloud …

  • Live Event Date: September 16, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you starting an on-premise-to-cloud data migration project? Have you thought about how much space you might need for your online platform or how to handle data that might be related to users who no longer exist? If these questions or any other concerns have been plaguing you about your migration project, check out this eSeminar. Join our speakers Betsy Bilhorn, VP, Product Management at Scribe, Mike Virnig, PowerSucess Manager and Michele …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds