Exercise 2: Create and Run Parallelized Tasks

The Parallel Extensions library provides a Task class that can be used to execute work items in parallel, across multiple cores. Basically, you can think of a Task object as a lightweight unit of work that might be scheduled to run in parallel to other units, if the TaskManager decides it is necessary.

As Task objects are created you need to supply them with a delegate or lambda statement containing the logic to execute. Then the TaskManager, which is the real heart of the Parallel Extensions library, will schedule the Task to execute, possibly on a different thread running on a different core.

Note:
To verify that each step is correctly performed, it is recommended to build the solution at the end of each task.

Task 1 – Natively Running Parallelized Tasks

  1. Open Microsoft Visual Studio 2010 from Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010.
  2. Open the solution file ParallelExtLab.sln located underSource\Ex02-CreateAndRunParallelizedTasks\begin(choosingthe folder that matches the language of your preference). Optionally, you can continue working with the solution you created in the previous exercise.
  3. Replace the current method calls from Main(), with a call to Ex2Task1_NativeParallelTasks() method.

    C#

    static void Main(string[] args)
    FakePre-df7605eacff24f4ab6611cd537f230aa-1e14746527414fada1efdf2b7a7bd2ddFakePre-3eaab023df5a4df9b90b9ef9f6a78b60-a0e41f9452b0423980528a5b4f484440FakePre-952d6f6df41c4d7db90713100a5e5e6c-eba7a3866cc6486e8ab9dc093198461b Ex2Task1_NativeParallelTasks();FakePre-2ee316acc3c04b1486775dae2f9b21bb-70f152da6ea042b989320f88229a0e7cFakePre-66930a88cd8d4762b1c779e09f729f51-68060a71abd94835a1b60cf6f7745dd4

    Visual Basic

    Sub Main(ByVal args() As String)
    FakePre-75e0d33e56be48989d62dd00e471e9b8-20da44d086394def9092b5d1be3bae29FakePre-39292720fbd7431596ed97547c2a9ea9-0f5658a342824c35b789305bbc9b1b40 Ex2Task1_NativeParallelTasks()FakePre-fc5648affb7b4486b1676f296123ecb1-e73994609c5d4f4fb9515814eac9753fFakePre-e16a34ce1df848aabcd7c72271e328d8-a16f05a35e064a239f45c4f583d0f00d

  4. Add the Ex2Task1_NativeParallelTasks method to Program.cs (C#) or Module1.vb (Visual Basic):

    (Code Snippet – Intro to Parallel Extensions Lab - Ex2 Ex2Task1_NativeParallelTasks CSharp)

    C#

    private static void Ex2Task1_NativeParallelTasks() { Task task1 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[0]); }); Task task2 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[1]); }); Task task3 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[2]); }); }

    (Code Snippet – Intro to Parallel Extensions Lab - Ex2 Ex2Task1_NativeParallelTasks VB)

    Visual Basic

    Private Sub Ex2Task1_NativeParallelTasks() Dim task1 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(0))) Dim task2 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(1))) Dim task3 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(2))) End Sub

  5. Build and run the application.
  6. You should observe that when run in parallel, some of the tasks might not complete execution until after the Ex2Task1_NativeParallelTasks method has exited and control has returned to Main. Because of this, the output time also does not reflect the total processing time as it is likely the tasks have not completed before returning to Main.

    Figure 7

    Output from running several Tasks in parallel

Task 2 – Using the Wait() and WaitAll() Methods

The benefit of executing tasks in parallel is faster execution and the ability to leverage multi-core processors. However, you should also notice that the current implementation introduces the possibility the main application could exit before the thread processing the task finishes.

You can handle this possible situation by invoking the Wait() method on the individual Task objects. This causes the main thread to wait until the indicated tasks are complete before continuing on to the next instruction.

  1. Replace the current method calls from Main(), with a call to Ex2Task2_WaitHandling(). This code will add wait handling to your example.

    C#

    static void Main(string[] args)
    FakePre-4b9ceaa78723497380049e854f420dd9-84d290212589450d8dda0e326dc6f093FakePre-5a38ce1154f64ed4aeebc3485dd4dd18-a5169ade89cc4a4bb9f8b3da46a48e44FakePre-1050e6b589e14533bd49f9ef7da877a1-8e04f230fe204492868b4637b897552a Ex2Task2_WaitHandling();FakePre-7708d125f54b493d92b78543f3e3dd8d-f240c65782cb4fb1b2762255fc387810FakePre-fbbb5837f5b14ade9244842d9f48a553-69852b11b01f4d9d987fdcf306b04538

    Visual Basic

    Sub Main(ByVal args() As String)
    FakePre-96a4abc7d6e94caa9a86b382c929897e-d9702154ed9344d7995cb2b0b30dda79FakePre-8f92e41853e244dab4c5f8e11072614d-50b7eb3a9b104d53995de0c085aa16fa Ex2Task2_WaitHandling()FakePre-f39e4adf7a8a49f0ac6e8070a408379f-5aa80a2df7c0464da50fd35da6616039FakePre-23ed398537744791b76e64fabc24b0e3-2bc53113789641749c152692b362904e

  2. Add the Ex2Task2_WaitHandling() method to Program.cs (C#) or Module1.vb (Visual Basic):

    (Code Snippet – Intro to Parallel Extensions Lab - Ex2 Ex2Task2_WaitHandling CSharp)

    C#

    private static void Ex2Task2_WaitHandling() { Task task1 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[0]); }); Task task2 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[1]); }); Task task3 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[2]); }); task1.Wait(); task2.Wait(); task3.Wait(); }

    (Code Snippet – Intro to Parallel Extensions Lab - Ex2 Ex2Task2_WaitHandling VB)

    C#

    Private Sub Ex2Task2_WaitHandling() Dim task1 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(0))) Dim task2 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(1))) Dim task3 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(2))) task1.Wait() task2.Wait() task3.Wait() End Sub

  3. Build and run the application.
  4. You should observe that this time all three Tasks completed before the final time was reported, indicating the end of the main thread.

    Figure 8

    Output from running tasks in parallel with individual Wait() conditions

    Note:
    The main thread waited for the created Task objects to complete before continuing operation. This approach is much simpler and cleaner than using ThreadPool.QueueUserWorkItem, which involves the creation and management of manual reset events, possible with the addition of Interlocked operations as well.

  5. In addition to the Wait() method on the individual Task objects, the static Task class also offers a WaitAll() method allowing you to wait on a specified list of tasks with one call. To see this method in action, remove the individual calls to Wait() for task1, task2, and task3 and replace them with the following:

    C#

    static void Main(string[] args)
    FakePre-178c9d0684064994974de73a27820cea-b802bf6423dd42a3b70bf523b1dad336FakePre-128243b4d19c4e82a9bb705350bb2107-a3117f84865442fd9178f09e84b07666FakePre-8aa2ac7d7dd7494d9e6965063e92ca63-68c50f029f574a6f8a1fc133112e44a8 Ex2Task2_WaitHandlingWaitAll();FakePre-79028ee178834cb394ff684d902519a1-fdf394b02314481eaa81f472bfedbffbFakePre-88dd2b633d2c435ba4530b1c8139bd49-855c7bf16a8b45da8d56b0f3bd0496b7

    Visual Basic

    Sub Main(ByVal args() As String)
    FakePre-490bfad1fe544442a06fed19eb53c3d9-fbca2385ff7a40ab968f82df5703b8c5FakePre-cf125322062646bbadece78478b9c0c4-1d11f2e802134c1c8e5f20179a11a0a6 Ex2Task2_WaitHandlingWaitAll()FakePre-0e958263b9b542dcb9e8f9eaab769ed4-a73bb41ad7ba4eed9198e1d831ecdeddFakePre-d1584559a65e43799f3b1bbd83560e93-be47dbd3ffac4d89870f4e87b888b29a

  6. Add the Ex2Tas2_WaitHandlingWaitAll() method to Program.cs (C#) or Module1.vb (Visual Basic):

    (Code Snippet – Intro to Parallel Extensions Lab - Ex2 Ex2Task2_WaitHandlingWaitAll CSharp)

    C#

    private static void Ex2Task2_WaitHandlingWaitAll() { Task task1 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[0]); }); Task task2 = Task.Factory.StartNew (delegate { PayrollServices.GetPayrollDeduction(employeeData[1]); }); Task task3 = Task.Factory.StartNew (delegate { PayrollServices.GetPayrollDeduction(employeeData[2]); }); Task.WaitAll(task1, task2, task3); }

    (Code Snippet – Intro to Parallel Extensions Lab - Ex2 Ex2Task2_WaitHandlingWaitAll VB)

    Visual Basic

    Private Sub Ex2Task2_WaitHandlingWaitAll() Dim task1 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(0))) Dim task2 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(1))) Dim task3 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(2))) Task.WaitAll(task1, task2, task3) End Sub

  7. Build and run the application.
  8. You should observe the main application waits until all individual Tasks are completed before continuing.

    Figure 9

    Output from running tasks in parallel with the WaitAll() method

Task 3 – Using the IsCompleted Property

There will be times when you want to check on the completion status of a Task before doing other work (for instance, you may have another task to run that is dependent on the first task completing first), but you may not want to utilize the Wait() method because Wait() blocks execution on the thread you’ve launched your Task from. For these situations, the Task class exposes an IsCompleted property. This enables you to check whether Task objects have completed their work before you continue with other processing.

  1. Replace the current method calls from Main(), with a call to Ex2Task3_TaskIsCompleted().

    C#

    static void Main(string[] args)
    FakePre-53e1f711f1cc4cb8a997ecd4229a3801-287d7a39fd5541f088e8f55e6cd0f522FakePre-940d2d55d59c4ca2bad47c948961abdf-84f39e929fe8483291ca803e6b5405deFakePre-ade42d2ade584c05a394ccc765f7cb62-29efee9eaddb430983f869d01641051d Ex2Task3_TaskIsCompleted();FakePre-852417d720d24e2fa477f4547c376028-18f287509bb240478088008e9378bb3fFakePre-94c3ba30465d4e1ca4e448ccda3b644c-b6a7303bce2a41e589ddafdadb21ea6d

    Visual Basic

    Sub Main(ByVal args() As String)
    FakePre-7705c2bebe1d4333a54355cd0f91f554-d170ec0cabd04667a55424e790e4f7d8FakePre-b5e412803d1b4cad95ee74a683d3ea50-3f922964f2144780aa58d5d02ea5ed03 Ex2Task3_TaskIsCompleted()FakePre-154072a870bd4d34876f6f17e016cb8b-20621ef7737b4a89ae36b9deaaa9fae6FakePre-4e9bc84ff684411f8cd724f4167c96dc-f6c29c21664d4f9c8d869a4dd129d3d3

  2. Add the Ex2Task3_TaskIsCompleted() method to Program.cs (C#) or Module1.vb (Visual Basic):

    (Code Snippet – Intro to Parallel Extensions Lab - Ex2 Ex2Task3_TaskIsCompleted CSharp)

    C#

    private static void Ex2Task3_TaskIsCompleted() { Task task1 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[0]); }); while (!task1.IsCompleted) { Thread.Sleep(1000); Console.WriteLine("Waiting on task 1"); } Task task2 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[1]); }); while (!task2.IsCompleted) { Thread.Sleep(1000); Console.WriteLine("Waiting on task 2"); } Task task3 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[2]); }); while (!task3.IsCompleted) { Thread.Sleep(1000); Console.WriteLine("Waiting on task 3"); } }
    FakePre-d206e133309343f2b085823f984099a1-193cedad367943e8957846e9ea018482FakePre-dfa08b4417a840fe9be7e64ac64fd352-cb2fbbd0161d4e1ab50ef3d846328a9d
    

    (Code Snippet – Intro to Parallel Extensions Lab - Ex2 Ex2Task3_TaskIsCompleted VB)

    Visual Basic

    Private Sub Ex2Task3_TaskIsCompleted() Dim task1 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(0))) Do While Not task1.IsCompleted Thread.Sleep(1000) Console.WriteLine("Waiting on task 1") Loop Dim task2 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(1))) Do While Not task2.IsCompleted Thread.Sleep(1000) Console.WriteLine("Waiting on task 2") Loop Dim task3 As Task = Task.Factory.StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(2))) Do While Not task3.IsCompleted Thread.Sleep(1000) Console.WriteLine("Waiting on task 3") Loop End Sub
    FakePre-0c5eba79c6fe4e5b95b38ad9c73c9a47-1d77dce1e90a47dbac4aa23dcc7a37f0FakePre-07a2e9e1b20247d5ba0dc4d29e24863e-27ea68361709441b80a13df5ac2d8a9b
    

  3. Build and run the application.
  4. You should observe that Tasks two and three do not start until the previous Task’s IsCompleted property is true.

    Figure 10

    Output from running tasks in parallel and utilizing the IsCompleted property

Task 4 – Using the ContinueWith() Method

While the IsCompleted property is useful for polling a Task to see if it is finished in order to be able to fire off more work, the Task class offers an even more convenient option. Using the ContinueWith() method makes it easy to string tasks together to run in a specific order.

The functionality passed in as arguments to the ContinueWith() method will be executed once the Task object’s logic continues.

  1. Replace the current method calls from Main(), with a call to Ex2Task4_ContinueWith().

    C#

    static void Main(string[] args)
    FakePre-31e2fc458c4e4af983896684aa68c758-b50e2ad0a7a44dd690515bcb0e3ee73eFakePre-1e2c531a310e451b92786014643901c8-f5f8e64f1a2a4593886b8e09a0231b76FakePre-85d5a67875944c9c92cbd1c2959c4ed2-6030b22d7b204d259c74d2e786ac7045 Ex2Task4_ContinueWith();FakePre-e7b6f87591504731abe53488784c66de-4eb8339491ec484b998f0ca7da7bcdf1FakePre-8c74c409a5464ee8a4a85785c9d5f050-c35ec5eec3894fba86fd4b7590aadffa

    Visual Basic

    Sub Main(ByVal args() As String)
    FakePre-0d5331b6a2df40a79b0592355b5065f8-ea7c4dceb9b2436e8b3ecb03a4bc6a3bFakePre-4dfd6b9a241f4d86995a17967198d1cd-9823de8a977048ac85bcaca8e2b60462 Ex2Task4_ContinueWith()FakePre-4028a4aeeee140edbdee23102dba4f9c-6591337206274e56a94c7b5b8ac6bf55FakePre-50dac49edf164bd098478354a9c85c4b-bd5dd13a934e483a915e5f339badeb60

  2. Add the Ex2Task4_ContinueWith() method to Program.cs (C#) or Module1.vb (Visual Basic):

    (Code Snippet – Intro to Parallel Extensions Lab - Ex2 Ex2Task4_ContinueWith CSharp)

    C#

    private static void Ex2Task4_ContinueWith() { Task task3 = Task.Factory.StartNew(delegate { PayrollServices.GetPayrollDeduction(employeeData[0]); }) .ContinueWith(delegate { PayrollServices.GetPayrollDeduction(employeeData[1]); }) .ContinueWith(delegate { PayrollServices.GetPayrollDeduction(employeeData[2]); }); task3.Wait(); }

    (Code Snippet – Intro to Parallel Extensions Lab - Ex2 Ex2Task4_ContinueWith VB)

    Visual Basic

    Private Sub Ex2Task4_ContinueWith() Dim task3 As Task = Task.Factory _ .StartNew(Sub() PayrollServices.GetPayrollDeduction(employeeData(0))) _ .ContinueWith(Sub() PayrollServices.GetPayrollDeduction(employeeData(1))) _ .ContinueWith(Sub() PayrollServices.GetPayrollDeduction(employeeData(2))) task3.Wait() End Sub

    Note:
    Here you created the first Task as normal, but you used the ContinueWith() method to have the runtime execute the subsequent calls in order.

  3. Build and run the application.
  4. You should observe that the tasks execute in order – employee 1 first, followed by employee 2, and finally employee 3.

    Figure 11

    Output from running tasks in parallel and using ContinueWith to ensure their order and wait conditions

Next Step:

Exercise 3: Use the Generic Task Class to Create and Run Tasks that Return a Value