ENTERPRISE

Parallel Programming : Task Relationships (part 1) - Continuation Tasks

11/15/2011 9:04:36 AM
You can start multiple tasks with the Parallel.Invoke method, create tasks with TaskFactory.StartNew, or use the Task constructor. Each approach varies slightly in functionality but ultimately creates a task that is scheduled and eventually started. So far, the example tasks have been independent, with no relationship to another task. However, tasks can have relationships. You can create continuationsubtasks, and tasks that have a parent-child relationship. Task relationships help you create more sophisticated solutions. tasks,

A continuation task automatically starts after another task completes. For example, the first task might be responsible for calculating a result, and then the second task might display the result. An error might occur if the result is shown before the calculation is complete. For this reason, it is important to order the execution of these two tasks.

1. Continuation Tasks

Ordering parallel tasks is sometimes helpful. Naturally, executing tasks in parallel is preferable; however, for correctness, ordering of tasks is sometimes required. The next image depicts four tasks. Two of the tasks are compute bound and return a result. The other two tasks are responsible for displaying the results. As shown, the four tasks are running in parallel, which could cause problems.

The tasks should be ordered so that the display tasks start after their corresponding compute tasks. In this scenario, the compute task is termed the antecedent, and the display task is called the successor. An antecedent is the first task in an ordered sequence. The successor task is the second and is a continuation of the antecedent task. As illustrated, TaskB should continue TaskA, and TaskD should continue TaskC.

The Task class has several methods that order tasks. These methods schedule one task to continue after another. The ContinueWith method, which is an instance method, is the simplest of them. Call Task.ContinueWith on the antecedent task. As a parameter, pass in the successor method as a delegate. The parameter is used to create the successor task that will continue after the antecedent task. Inside the successor task, you can reference the antecedent task, which is provided as a parameter.

Create an antecedent and successor task and then wait for both to complete

  1. Create a console application. In the Main function, create a new task by using the Task constructor. Initialize the task with a lambda expression. In the lambda expression, display the name of the task.

    var antecedent = new Task(() =>{
    Console.WriteLine("antecedent.");
    });

  2. Use the Task.ContinueWith method to create a continuation task. In the lambda expression for this task, display the name of the task. This task automatically runs when the antecedent task completes.

    var successor=antecedent.ContinueWith((firstTask) =>
    { Console.WriteLine("successor."); });

  3. You can now start the antecedent task. Afterward, wait for both the antecedent and successor tasks to complete.

    class Program
    {
    static void Main(string[] args)
    {
    var antecedent = new Task(() =>
    {
    Console.WriteLine("antecedent.");
    });
    var successor=antecedent.ContinueWith((firstTask) =>
    { Console.WriteLine("successor."); });
    antecedent.Start();
    Task.WaitAll(antecedent, successor);
    }
    }

In the previous example code, the antecedent task did not return a result. When the antecedent returns a value, the successor task can find the results of the antecedent task in the Task.Result property. Remember, the successor gets a reference to the antecedent as a parameter.

Create an antecedent task and a successor task in which the antecedent task result is checked later

  1. Create a console application. In the Main function, define a new task by using the Task constructor. The task should return an integer value. Initialize the task with a lambda expression. In the lambda expression, display the current task and return a value. This is the antecedent task.

    Task calculate = new Task(() =>{
    Console.WriteLine("Calculate result.");
    return 42;});

  2. With the Task.ContinueWith method, create a continuation task that displays the result of the antecedent task. Pass a reference to the antecedent as a parameter. You can use the reference to access the result of the antecedent.

    var answer=calculate.ContinueWith((antecedent) =>{
    Console.WriteLine("The answer is {0}.", antecedent.Result); });

  3. You can now start the antecedent task. Then wait for both the antecedent and successor methods to complete.

    class Program
    {
    static void Main(string[] args)
    {
    Task calculate = new Task(() =>
    {
    Console.WriteLine("Calculate result."); return 42;
    });
    Task answer=calculate.ContinueWith((antecedent) =>{
    Console.WriteLine("The answer is {0}.", antecedent.Result); });
    calculate.Start();
    Task.WaitAll(calculate, answer);
    }
    }


In the preceding example, the successor task started when the current task completed. What if you want to continue only after several tasks finish? That is possible with the static TaskFactory.ContinueWhenAll method. This method accepts an array of tasks as a parameter. The continuation task will begin after the last of these tasks has completed. In this case, the successor task receives an array of antecedent tasks as a parameter. You can use this array in the successor to access state information from each antecedent.

Create two antecedent tasks and check the result of both in the successor task

  1. Before the Main function, add a PerformCalculation method that returns an integer value of 42.

    static int PerformCalculation() { return 42; }

  2. Next, create a new task that returns an integer value. Initialize the task with a lambda expression. In the lambda expression, display the current task and return the result of the PerformCalculation method.

    Task TaskA = new Task(() =>{
    Console.WriteLine("TaskA started.");
    return PerformCalculation(); });

  3. Now create a TaskB, similar to TaskA. TaskA and TaskB are the antecedent methods.

    Task TaskB = new Task(() => {
    Console.WriteLine("TaskB started.");
    return PerformCalculation(); });

  4. Create a continuation task to run after TaskA and TaskB have completed. You can accomplish this with the TaskFactory.ContinueWhenAll method. The first parameter is an array of tasks.

    Task total=Task.Factory.ContinueWhenAll(new Task[] { TaskA, TaskB },

  5. As part of TaskFactory.ContinueWhenAll, you next define the continuation task as a lambda expression. Pass a reference to the antecedent tasks as the parameter. In the successor task, add and display the results of the antecedent tasks.

    (tasks)=>Console.WriteLine("Total = {0}", tasks[0].Result+tasks[1].Result));


  6. Start both antecedent tasks and then wait for both to complete. Afterward, wait for the continuation task to complete.

    class Program
    {
    static int PerformCalculation() { return 42; }
    static void Main(string[] args)
    {
    Task TaskA = new Task(() =>
    {
    Console.WriteLine("TaskA started.");
    return PerformCalculation();
    });
    Task TaskB = new Task(() =>
    {
    Console.WriteLine("TaskB started.");
    return PerformCalculation();
    });
    Task total=Task.Factory.ContinueWhenAll(new Task[] { TaskA, TaskB },
    (tasks) => Console.WriteLine(
    "Total = {0}", tasks[0].Result + tasks[1].Result));
    TaskA.Start();
    TaskB.Start();
    Task.WaitAll(TaskA, TaskB);
    total.Wait();
    }
    }


As shown, TaskFactory.ContinueWhenAll starts the continuation task (successor) after all the antecedent tasks have completed. Alternatively, there’s a TaskFactory.ContinueWhenAny, which is an instance method that starts the continuation task after any listed antecedent task completes.

An interesting option when continuing a task is the TaskContinuationOptions parameter. With this option, you can set an event as an additional criterion for starting the continuation task. For example, suppose you want to continue a task only when the antecedent raises an exception. As another example, you might want to continue a task only when the antecedent was not canceled. TaskContinuationOptions is an enumeration that covers both of these scenarios and more. Here are the possible values.

  • None

  • AttachedToParent

  • ExecuteSynchronously

  • LongRunning

  • NotOnCanceled

  • NotOnFaulted

  • NotOnRanToCompletion

  • OnlyOnCanceled

  • OnlyOnFaulted

  • OnlyOnRanToCompletion

  • PreferFairness

Some of the values, such as OnlyOnFaulted, are not available for continuation or successor tasks with multiple antecedents.

Here is a scenario. Assume that you want to perform a rollback if a task throws an unhandled exception. Exceptions can sometimes leave objects in an unknown state, so you might want to return objects to a known state; being in an unknown state is rarely good for a program. If an unhandled exception occurs in a task, you can perform the rollback in a continuation task, by using the TaskContinuationOptions.OnlyOnFaulted enumeration value.

Implement the preceding scenario in an application

  1. Create a console application. You need to implement a custom task that can perform a rollback of an operation. First, define a new CustomTask class. Inherit the class from the Task class.

    class CustomTask : Task {
    }

  2. Implement a public one-argument constructor for the CustomTask class with an Action delegate as the parameter. Pass the Action delegate to the base class (Task) constructor.

    public CustomTask(Action action)
    : base(action)
    { }

  3. Add a PerformRollback method to the CustomTask class as a member method. In the real world, this method would perform a rollback. In our example, it simply displays a message.

    public void PerformRollback() { Console.WriteLine("Rollback..."); }

  4. In the Main function, create a new CustomTask. This is the antecedent task. In the lambda expression for the task, throw an unhandled exception.

    CustomTask antecedent = new CustomTask(() => {
    throw new Exception("Unhandled"); });

  5. Next, create a continuation task for the antecedent task by using the Task.ContinueWith method. Implement the continuation task as a lambda expression. Pass a reference of the antecedent task as a parameter of the lambda expression. In our example, you want to perform a rollback. Finally, you want to execute the continuation task only when the antecedent task has a fault. For that reason, add the TaskContinuationOptions.OnlyOnFaulted as the final parameter.

    antecedent.ContinueWith((predTask) =>
    {
    ((CustomTask)predTask).PerformRollback();
    }, TaskContinuationOptions.OnlyOnFaulted);

  6. Now you can start the antecedent task. Wait for the task in a try/catch block.

    class CustomTask : Task {
    public CustomTask(Action action)
    : base(action)
    { }
    public void PerformRollback() { Console.WriteLine("Rollback..."); }
    }
    class Program
    {
    static void Main(string[] args)
    {
    CustomTask antecedent = new CustomTask(() =>
    {
    throw new Exception("Unhandled");
    });
    antecedent.ContinueWith((predTask) =>
    {
    ((CustomTask)predTask).PerformRollback();
    },
    TaskContinuationOptions.OnlyOnFaulted);
    antecedent.Start();
    try
    {
    antecedent.Wait();
    }
    catch (AggregateException ex)
    {
    }
    }
    }
Other  
  •  BizTalk 2006 : Handling Ordered Delivery
  •  BizTalk 2006 : Implementing Dynamic Parallel Orchestrations
  •  Windows System Programming : The Registry
  •  Windows System Programming : File Locking
  •  SharePoint 2010 : Security - Secure Store Service & Using SSS with BCS
  •  SharePoint 2010 : Security - Claims Based Authentication
  •  SharePoint 2010 : PerformancePoint Services (part 2) - Using PerformancePoint
  •  SharePoint 2010 : PerformancePoint Services (part 1) - PerformancePoint Central Administration Settings
  •  Windows System Programming : Example: Listing File Attributes & Setting File Times
  •  Windows System Programming : File Attributes and Directory Processing
  •  Windows System Programming : File Pointers & Getting the File Size
  •  SharePoint 2010 : Business Intelligence - Excel Services (part 2) - Accessing Excel Services Over SOAP
  •  SharePoint 2010 : Business Intelligence - Excel Services (part 1) - Accessing Excel Services Over REST
  •  SharePoint 2010 : Business Intelligence - Visio Services
  •  Exchange Server 2010 : Perform Essential Database Management (part 3) - Manage Database Settings
  •  Exchange Server 2010 : Perform Essential Database Management (part 2) - Manage the Transaction Log Files
  •  Exchange Server 2010 : Perform Essential Database Management (part 1) - Manage the Database Files
  •  Architecting Applications for the Enterprise : UML Diagrams (part 3) - Sequence Diagrams
  •  Architecting Applications for the Enterprise : UML Diagrams (part 2) - Class Diagrams
  •  Architecting Applications for the Enterprise : UML Diagrams (part 1) - Use-Case Diagrams
  •  
    Top 10
    Nikon 1 J2 With Stylish Design And Dependable Image And Video Quality
    Canon Powershot D20 - Super-Durable Waterproof Camera
    Fujifilm Finepix F800EXR – Another Excellent EXR
    Sony NEX-6 – The Best Compact Camera
    Teufel Cubycon 2 – An Excellent All-In-One For Films
    Dell S2740L - A Beautifully Crafted 27-inch IPS Monitor
    Philips 55PFL6007T With Fantastic Picture Quality
    Philips Gioco 278G4 – An Excellent 27-inch Screen
    Sony VPL-HW50ES – Sony’s Best Home Cinema Projector
    Windows Vista : Installing and Running Applications - Launching Applications
    Most View
    Bamboo Splash - Powerful Specs And Friendly Interface
    Powered By Windows (Part 2) - Toshiba Satellite U840 Series, Philips E248C3 MODA Lightframe Monitor & HP Envy Spectre 14
    MSI X79A-GD65 8D - Power without the Cost
    Canon EOS M With Wonderful Touchscreen Interface (Part 1)
    Windows Server 2003 : Building an Active Directory Structure (part 1) - The First Domain
    Personalize Your iPhone Case
    Speed ​​up browsing with a faster DNS
    Using and Configuring Public Folder Sharing
    Extending the Real-Time Communications Functionality of Exchange Server 2007 : Installing OCS 2007 (part 1)
    Google, privacy & you (Part 1)
    iPhone Application Development : Making Multivalue Choices with Pickers - Understanding Pickers
    Microsoft Surface With Windows RT - Truly A Unique Tablet
    Network Configuration & Troubleshooting (Part 1)
    Panasonic Lumix GH3 – The Fastest Touchscreen-Camera (Part 2)
    Programming Microsoft SQL Server 2005 : FOR XML Commands (part 3) - OPENXML Enhancements in SQL Server 2005
    Exchange Server 2010 : Track Exchange Performance (part 2) - Test the Performance Limitations in a Lab
    Extra Network Hardware Round-Up (Part 2) - NAS Drives, Media Center Extenders & Games Consoles
    Windows Server 2003 : Planning a Host Name Resolution Strategy - Understanding Name Resolution Requirements
    Google’s Data Liberation Front (Part 2)
    Datacolor SpyderLensCal (Part 1)