Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Parallel Extensions to the .NET 4.0 Runtime

3.54/5 (12 votes)
6 Nov 2010CPOL5 min read 1  
An article that mainly focuses on the TPL

General Introduction

This article will mainly focus on the Task Parallel Library – its usage and its place in parallel programming. To prevent avoiding some necessary constructs, this article will also (briefly) touch PLINQ and the Parallel class’s For method. Microsoft introduced some very necessary additions to its recent release of the .NET 4.0 Runtime to keep in step with the multi-core processor technology. Programmers not only face a slight change in technology, but will eventually have to adapt to some patterns in parallel programming. These additions can be broken down into four major categories:

  1. A collection of task-oriented APIs, called the task parallel library (TPL). A task object model is available to frame your thoughts about parallel programming, in addition to helper classes with common imperative (not declarative) data parallel operations like parallel for loops. These constructs and multithreading APIs, along with the Parallel class, form the Parallel Framework (PFX).
  2. A data parallel implementation of .NET’s LINQ. The Parallel LINQ (PLINQ) query provider takes any LINQ-to-Objects query over in memory data structures and auto-parallelizes it indirectly using TPL.
  3. A full collection of synchronization primitives that encapsulate common coordination events.
  4. A set of concurrent collections

The Task Parallel Library

The unit of concurrency in TPL is a Task object. This class offers much of the capabilities and, like most of the other TPL classes, can be found in the System.Threading.Tasks namespace.

C#
public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable
{

//Constructors
public Task(Action action);
public Task(Action<Object> action, Object state);

public Task(Action action);
public Task(Action<Object> action, Object state);
public Task(Action action, TaskManager, taskManager);
public Task(Action<Object> action,Object state,TaskCreationOptions creationOptions)
public Task(
Action action,
TaskManager, taskManager,
TaskCreationOptions, options
);
// static factory methods
public static Task StartNew(Action action); 
public static Task StartNew(Action<Object> action, Object state);
public static Task StartNew(Action action, Action action; 
public static Task StartNew(
Action action,
TaskManager, taskManager
TaskCreationOptions, options
);
public static Task StartNew(
Action<Object> action,
object state,
TaskManager, taskManager
TaskCreationOptions, options
);
// Methods
public void Cancel();
public void CancelAndWait();
public bool CancelAndWait(int millisecondTimeout;
public bool CancelAndWait(TimeSpan timeout);
public Task ContinueWith(Action<Task> action);
public Task ContinueWith(
Action<Task> action,
TaskContinuation kind
);
public Task ContinueWith(
Action<Task> action,
TaskContinuation kind,
TaskCreationOptions, options
);
public Task ContinueWith(
Action<Task> action,
TaskContinuation kind,
TaskCreationOptions, options,
bool executeSynchronosuly
);
public void Dispose();
public void Start();
public void Wait();
public bool Wait(int millisecondTimeout);
public bool Wait(TimeSpan timeout);
public static void WaitAll(params Task[] tasks); 
public static bool WaitAll(Task[] tasks, int millisecondTimeout);
public static bool WaitAll(Task[] tasks, TimeSpan timeout);
public static void WaitAny(params Task[] tasks); 
public static bool WaitAny(Task[] tasks, int millisecondTimeout);
public static bool WaitAny(Task[] tasks, TimeSpan timeout);
// Properties
public static Task Current { get; }
public static Exception { get; }
public int Id { get; }
public bool IsCanceled { get; }
public bool IsCancellationRequested; { get; }
public bool Iscompleted { get; }
public Task Parent { get; }
public TaskStatus Status { get; }
public TaskCreationOptions TaskCreationOptions { get; }
}

The first aspects to Task are the constructors and static StartNew factory methods. Both offer the same overloads; the StartNew methods are just actually short-cuts for the common operation of constructing a new task and immediately invoking its Start method. The code example below is referenced directly from the MSDN library. When a Task object is created, the Start() begins the task scheduling process.But any reader is probably wondering how a Task is created. Once things are in order to be parallelized, the set of code instructions that comprise the thread are assigned to the task. When a Task returns a result, it is in the form of Task<tresult>, rather than the action delegate. Operations are blocked until the Task is complete. Tasks can be chained together so the return of one Task is a Task. There will be original content in this article, but this code example will be followed with an explanation to help clarify how and why it works in the manner that it does:

C#
using System; 
using System.Threading; 
using System.Threading.Tasks; 
class StartNewDemo 
{ 
// Demonstrated features: 
// Task ctor() 
// Task.Factory 
// Task.Wait() 
// Task.RunSynchronously() 
// Expected results: 
// Task t1 (alpha) is created unstarted. 
// Task t2 (beta) is created started. 
// Task t1's (alpha) start is held until after t2 (beta) is started. 
// Both tasks t1 (alpha) and t2 (beta) are potentially executed on threads 
// other than the main thread on multi-core machines. 
// Task t3 (gamma) is executed synchronously on the main thread. 
static void Main() 
{ 
Action<object> action = (object obj) => 
{ 
Console.WriteLine("Task={0}, obj={1}, Thread={2}", 
	Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId); 
}; 
// Construct an unstarted task 
Task t1 = new Task(action, "alpha"); 
// Construct a started task 
Task t2 = Task.Factory.StartNew(action, "beta"); 
// Block the main thread to demonstrate that t2 is executing 
t2.Wait(); 
// Launch t1 
t1.Start(); 
Console.WriteLine("t1 has been launched. (Main Thread={0})", 
	Thread.CurrentThread.ManagedThreadId); 
// Wait for the task to finish. 
// You may optionally provide a timeout interval or a cancellation token 
// to mitigate situations when the task takes too long to finish. 
t1.Wait(); 
// Construct an unstarted task 
Task t3 = new Task(action, "gamma"); 
// Run it synchronously 
t3.RunSynchronously(); 
// Although the task was run synchronously, 
// it is a good practice to wait for it which observes for 
// exceptions potentially thrown by that task. 
t3.Wait(); 
   } 
} 

OUTPUT

Task=1, obj=beta, Thread=3
t1 has been launched. (Main Thread=1)
Task=2, obj=alpha, Thread=3
Task=3, obj=gamma, Thread=1

OK. Now notice that:

  • An Action must be given for every new task. This is a delegate that will run once the task actually goes on.
  • Optionally, an object state argument can be supplied. This is for those overloads that take an Action<object> and the value, of course, is passed though the delegate as a whole. Recall that a method call through delegation splits up the definition of the method call and the invocation of the call into two parts.
  • A TaskManager object may be supplied.

The task parallelism constructs are useful when you want to run some operation on a pooled thread, and also to manage a task’s workflow through continuations and parent/child tasks. PLINQ and the Parallel class are useful whenever you want to execute operations in parallel and then wait for them to complete (structured parallelism). This includes non-CPU-intensive tasks such as calling a web service.

Parallel For and Parallel ForEach

Parallel.For and Parallel.ForEach perform the equivalent of a C# for and foreach loop, but with each iteration executing in parallel instead of sequentially. Here are their (simplest) signatures:

C#
public static ParallelLoopResult For (
int fromInclusive, int toExclusive, Action<int> body)

public static ParallelLoopResult ForEach<TSource> (
IEnumerable<TSource> source, Action<TSource> body)

Our implementation of this method will invoke the body of the loop once per element in the range [inclusiveLowerBound,exclusiveUpperBound). The following sequential loop:

C#
for (int I = 0; I < 100; i++)
Foo();

is parallelized like this:

Parallel.For (0, 100, i => Foo (i));

First, we need a signature. To parallelize a for loop, we'll implement a method that takes three parameters: a lower-bound, an upper-bound, and a delegate for the loop body that accepts as a parameter an integral value to represent the current iteration index (that delegate will be invoked once for each iteration). Notice the iteration space is defined. Now briefly take a look at this PLINQ example:

C#
using System;
using System.Collections.Generic;
using System.Linq;
public class Program {
public static void Main() {
IEnumerable<int> numbers = Enumerable.Range (3, 100000-3);
var parallelQuery =
from n in numbers.AsParallel()
where Enumerable.Range (2, (int) Math.Sqrt (n)).All (i => n % i > 0)
select n;
int[] primes = parallelQuery.ToArray();

foreach ( var e in parallelQuery)
   {
  Console.WriteLine(e);
   }
  }
}

This outputs to:

  3
  5
  7
 11
 13
 17
 19
 23
29
31
37
41
43
47
53
……. and so on …
99667
99679
99689
99829
99833
99839
99859
99871
99877
99881
99901
99907
99923
99929

Parallel LINQ

LINQ permits us to write declarative (not imperative) queries either through a series of API calls to the System.Linq.Enumerable class, or by using the language comprehension syntax supported by languages like C#. The general concept of an enumerator is that of a type whose sole purpose is to advance through another collection’s contents, reading them one at a time. There are normally no write capabilities. PLINQ works by analyzing the query, and arranging for different pieces to run in parallel with one another on multiple processors. It does by ultimately using TPL under the hood. The example below references an Employee class. The actual program contains a sequence of private, static methods, all of which are written to exemplify the usage of the parallel constructs, PLINQ, the For method loops, and so forth. Notice that this class is written to then be passed as a parameter to another class, and so forth.

C#
using System;
using System.Collections.Generic;
public class Employee
{
 public string FirstName
 {
   get;
   set;
  }
public string LastName
 {
   get;
   set;
 }
public string Address
 {
   get;
   set;
 }
public DateTime HireDate
 {
   get;
   set;
  }
public int EmployeeID
 {
   get;
   set;
  }
 }
public class EmployeeList : List<Employee>
{
   public EmployeeList()
{
Add(new Employee { EmployeeID = 1, FirstName = "Jay", 
	LastName = "Adams", HireDate = DateTime.Parse("2007/1/1") });
Add(new Employee { EmployeeID = 2, FirstName = "Adam", 
	LastName = "Barr", HireDate = DateTime.Parse("2006/3/15") });
Add(new Employee { EmployeeID = 3, FirstName = "Karen", 
	LastName = "Berge", HireDate = DateTime.Parse("2005/6/17") });
Add(new Employee { EmployeeID = 4, FirstName = "Scott", 
	LastName = "Bishop", HireDate = DateTime.Parse("2000/3/19") });
Add(new Employee { EmployeeID = 5, FirstName = "Jo", 
	LastName = "Brown", HireDate = DateTime.Parse("2003/7/17") });
Add(new Employee { EmployeeID = 6, FirstName = "David", 
	LastName = "Campbell", HireDate = DateTime.Parse("2005/9/13") });
Add(new Employee { EmployeeID = 7, FirstName = "Rob", 
	LastName = "Caron", HireDate = DateTime.Parse("2002/12/3") });
Add(new Employee { EmployeeID = 8, FirstName = "Jane", 
	LastName = "Clayton", HireDate = DateTime.Parse("2008/7/1") });
Add(new Employee { EmployeeID = 9, FirstName = "Pat", 
	LastName = "Coleman", HireDate = DateTime.Parse("2008/1/7") });
Add(new Employee { EmployeeID = 10, FirstName = "Aaron", 
	LastName = "Con", HireDate = DateTime.Parse("2001/11/1") });
Add(new Employee { EmployeeID = 11, FirstName = "Don", 
	LastName = "Hall", HireDate = DateTime.Parse("2006/4/21") });
Add(new Employee { EmployeeID = 12, FirstName = "Joe", 
	LastName = "Howard", HireDate = DateTime.Parse("2006/7/19") });
Add(new Employee { EmployeeID = 13, FirstName = "Jim", 
	LastName = "Kim", HireDate = DateTime.Parse("2001/3/9") });
Add(new Employee { EmployeeID = 14, FirstName = "Eric", 
	LastName = "Lang", HireDate = DateTime.Parse("2005/7/15") });
Add(new Employee { EmployeeID = 15, FirstName = "Jose", 
	LastName = "Lugo", HireDate = DateTime.Parse("2003/8/6") });
Add(new Employee { EmployeeID = 16, FirstName = "Nikki", 
	LastName = "McCormick", HireDate = DateTime.Parse("2005/5/18") });
Add(new Employee { EmployeeID = 17, FirstName = "Susan", 
	LastName = "Metters", HireDate = DateTime.Parse("2002/8/5") });
Add(new Employee { EmployeeID = 18, FirstName = "Linda", 
	LastName = "MIctchell", HireDate = DateTime.Parse("2006/10/1") });
Add(new Employee { EmployeeID = 19, FirstName = "Kim", 
	LastName = "Ralls", HireDate = DateTime.Parse("2002/12/7") });
Add(new Employee { EmployeeID = 20, FirstName = "Jeff", 
	LastName = "Smith", HireDate = DateTime.Parse("2001/3/30") });
}
}
public class EmployeeHierarchy : Tree<Employee>
{
public EmployeeHierarchy()
{
//root
Data = new Employee { EmployeeID = 1, FirstName = "Jay", 
	LastName = "Adams", HireDate = DateTime.Parse("2007/1/1") };
//1st level
Left = new Tree<Employee>();
Right = new Tree<Employee>();
Left.Data = new Employee { EmployeeID = 2, FirstName = "Adam", 
	LastName = "Barr", HireDate = DateTime.Parse("2006/3/15") };
Right.Data = new Employee { EmployeeID = 17, FirstName = "Karen", 
	LastName = "Berge", HireDate = DateTime.Parse("2005/6/17") };
//2nd level
//left
Left.Left = new Tree<Employee>();
Left.Right = new Tree<Employee>();
Left.Left.Data = new Employee { EmployeeID = 3, FirstName = "Scott", 
	LastName = "Bishop", HireDate = DateTime.Parse("2000/3/19") };
Left.Right.Data = new Employee { EmployeeID = 14, FirstName = "Jo", 
	LastName = "Brown", HireDate = DateTime.Parse("2003/7/17") };
//right
Right.Left = new Tree<Employee>();
Right.Right = new Tree<Employee>();
Right.Left.Data = new Employee { EmployeeID = 18, FirstName = "David", 
	LastName = "Campbell", HireDate = DateTime.Parse("2005/9/13") };
Right.Right.Data = new Employee { EmployeeID = 19, FirstName = "Rob", 
	LastName = "Caron", HireDate = DateTime.Parse("2002/12/3") };
//3rd level
//left
//left.left
Left.Left.Left = new Tree<Employee>();
Left.Left.Right = new Tree<Employee>();
Left.Left.Left.Data = new Employee { EmployeeID = 4, FirstName = "Jane", 
	LastName = "Clayton", HireDate = DateTime.Parse("2008/7/1") };
Left.Left.Right.Data = new Employee { EmployeeID = 7, FirstName = "Pat", 
	LastName = "Coleman", HireDate = DateTime.Parse("2008/1/7") };
//left.right
Left.Right.Left = new Tree<Employee>();
Left.Right.Right = new Tree<Employee>();
Left.Right.Left.Data = new Employee { EmployeeID = 15, FirstName = "Aaron", 
	LastName = "Con", HireDate = DateTime.Parse("2001/11/1") };
Left.Right.Right.Data = new Employee { EmployeeID = 16, FirstName = "Don", 
	LastName = "Hall", HireDate = DateTime.Parse("2006/4/21") };
//4th level
//left.left.left
Left.Left.Left.Left = new Tree<Employee>();
Left.Left.Left.Right = new Tree<Employee>();
Left.Left.Left.Left.Data = new Employee { EmployeeID = 5, FirstName = "Joe", 
	LastName = "Howard", HireDate = DateTime.Parse("2006/7/19") };
Left.Left.Left.Right.Data = new Employee { EmployeeID = 6, FirstName = "Jim", 
	LastName = "Kim", HireDate = DateTime.Parse("2001/3/9") };
//left.left.right
Left.Left.Right.Left = new Tree<Employee>();
Left.Left.Right.Right = new Tree<Employee>();
Left.Left.Right.Left.Data = new Employee { EmployeeID = 8, FirstName = "Eric", 
	LastName = "Lang", HireDate = DateTime.Parse("2005/7/15") };
Left.Left.Right.Right.Data = new Employee { EmployeeID = 11, FirstName = "Jose", 
	LastName = "Lugo", HireDate = DateTime.Parse("2003/8/6") };
Left.Left.Right.Left.Left = new Tree<Employee>();
Left.Left.Right.Left.Right = new Tree<Employee>();
Left.Left.Right.Left.Left.Data = new Employee { EmployeeID = 9, FirstName = "Nikki", 
	LastName = "McCormick", HireDate = DateTime.Parse("2005/5/18") };
Left.Left.Right.Left.Right.Data = new Employee { EmployeeID = 10, FirstName = "Susan", 
	LastName = "Metters", HireDate = DateTime.Parse("2002/8/5") };
Left.Left.Right.Right.Left = new Tree<Employee>();
Left.Left.Right.Right.Right = new Tree<Employee>();
Left.Left.Right.Right.Left.Data = new Employee { EmployeeID = 12, FirstName = "Linda", 
	LastName = "MIctchell", HireDate = DateTime.Parse("2006/10/1") };
Left.Left.Right.Right.Right.Data = new Employee { EmployeeID = 13, FirstName = "Kim", 
	LastName = "Ralls", HireDate = DateTime.Parse("2002/12/7") };
}
}

public class Tree<T>
{
public T Data;
public Tree<T> Left, Right;
}

public static class PayrollServices
{
public static decimal GetPayrollDeduction(Employee employee)
{
Console.WriteLine("Executing GetPayrollDeduction for employee {0}", employee.EmployeeID);
var rand = new Random(DateTime.Now.Millisecond);
var delay = rand.Next(1, 5);
var count = 0;
var process = true;
while(process)
{
System.Threading.Thread.Sleep(1000);
count++;
if (count >= delay)
process = false;
}
return delay;
}
public static string GetEmployeeInfo(Employee employee)
{
Console.WriteLine("Executing GetPayrollDeduction for employee {0}", employee.EmployeeID);
//Random rand = new Random(System.DateTime.Now.Millisecond);
const int delay = 5;
var count = 0;
var process = true;
while (process)
{
System.Threading.Thread.Sleep(1000);
count++;
if (count >= delay)
process = false;
}

return string.Format("{0} {1}, {2}", employee.EmployeeID, 
		employee.LastName, employee.FirstName);
}
}

csc /t:Library EmployeeList.cs outputs EmployeeList.dll.

Now here is the code that references this DLL externally:

C#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    private static EmployeeList employeeData;

    static void Main(string[] args)
    {
        employeeData = new EmployeeList();

        Console.WriteLine("Payroll process started at {0}", DateTime.Now);
        var sw = Stopwatch.StartNew();

        // Methods to call
        // Ex1Task1_ParallelizeLongRunningService();
        // Ex1Task1_UseParallelForMethod();
        // Ex1Task1_StandardForEach();
        // Ex1Task1_ParallelForEach();
        // Ex1Task1_WalkTree();
        // Ex2Task1_NativeParallelTasks();
        // Ex2Task2_WaitHandling();
        // Ex2Task2_WaitHandlingWaitAll();
        // Ex2Task3_TaskIsCompleted();
        // Ex2Task4_ContinueWith();
        // Ex3Task1_TaskReturnValue();
        // Ex4Task1_PLINQ();
        // Ex4Task1_PLINQAsParallel();
        // Ex4Task2_Extensions();
        // Ex4Task2_ConvertToParallelExtensions();
        Ex4Task3_PLINQComprehensionSyntax();

        Console.WriteLine("Payroll finished at {0} and took {1}",
                              DateTime.Now, sw.Elapsed.TotalSeconds);
        Console.WriteLine();
        Console.ReadLine();
    }

    private static void Ex1Task1_ParallelizeLongRunningService()
    {
        Console.WriteLine("Non-parallelized for loop");

        for (int i = 0; i < employeeData.Count; i++)
        {
            Console.WriteLine("Starting process for employee id {0}",
                employeeData[i].EmployeeID);
            decimal span =
                PayrollServices.GetPayrollDeduction(employeeData[i]);
            Console.WriteLine("Completed process for employee id {0}" +
                "process took {1} seconds",
                employeeData[i].EmployeeID, span);
            Console.WriteLine();
        }
    }

    private static void Ex1Task1_UseParallelForMethod()
    {
        Parallel.For(0, employeeData.Count, i =>
        {
            Console.WriteLine("Starting process for employee id {0}",
                               employeeData[i].EmployeeID);
            decimal span =
                PayrollServices.GetPayrollDeduction(employeeData[i]);
            Console.WriteLine("Completed process for employee id {0}",
                              employeeData[i].EmployeeID);
            Console.WriteLine();
        });
    }

    private static void Ex1Task1_StandardForEach()
    {
        foreach (Employee employee in employeeData)
        {
            Console.WriteLine("Starting process for employee id {0}",
                employee.EmployeeID);
            decimal span =
                PayrollServices.GetPayrollDeduction(employee);
            Console.WriteLine("Completed process for employee id {0}",
                employee.EmployeeID);
            Console.WriteLine();
        }
    }

    private static void Ex1Task1_ParallelForEach()
    {
        Parallel.ForEach(employeeData, ed =>
        {
            Console.WriteLine("Starting process for employee id {0}",
                ed.EmployeeID);
            decimal span = PayrollServices.GetPayrollDeduction(ed);
            Console.WriteLine("Completed process for employee id {0}",
                ed.EmployeeID);
            Console.WriteLine();
        });
    }

    private static void Ex1Task1_WalkTree()
    {
        EmployeeHierarchy employeeHierarchy = new EmployeeHierarchy();
        WalkTree(employeeHierarchy);
    }

    private static void WalkTree(Tree<employee> node)
    {
        if (node == null)
            return;

        if (node.Data != null)
        {
            Employee emp = node.Data;
            Console.WriteLine("Starting process for employee id {0}",
                emp.EmployeeID);
            decimal span = PayrollServices.GetPayrollDeduction(emp);
            Console.WriteLine("Completed process for employee id {0}",
                emp.EmployeeID);
            Console.WriteLine();
        }

        Parallel.Invoke(delegate { WalkTree(node.Left); },
    delegate { WalkTree(node.Right); });
    }

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

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

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

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

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

    private static void Ex3Task1_TaskReturnValue()
    {
        Console.WriteLine("Calling parallel task with return value");
        var data = Task.Factory.StartNew(() =>
          PayrollServices.GetPayrollDeduction(employeeData[0]));
        Console.WriteLine("Parallel task returned with value of {0}",
            data.Result);
    }

    static void Ex4Task1_PLINQ()
    {
        var q = Enumerable.Select(
                  Enumerable.OrderBy(
                    Enumerable.Where(employeeData,
                    x => x.EmployeeID % 2 == 0),
                    x => x.EmployeeID),
                  x => PayrollServices.GetEmployeeInfo(x))
                  .ToList();

        foreach (var e in q)
        {
            Console.WriteLine(e);
        }
    }

    static void Ex4Task1_PLINQAsParallel()
    {
        var q = ParallelEnumerable.Select(
                ParallelEnumerable.OrderBy(
                  ParallelEnumerable.Where(employeeData.AsParallel(),
                    x => x.EmployeeID % 2 == 0),
                  x => x.EmployeeID),
                x => PayrollServices.GetEmployeeInfo(x))
              .ToList();

        foreach (var e in q)
        {
            Console.WriteLine(e);
        }
    }

    private static void Ex4Task2_Extensions()
    {
        var q = employeeData.
            Where(x => x.EmployeeID % 2 == 0).OrderBy(x => x.EmployeeID)
            .Select(x => PayrollServices.GetEmployeeInfo(x))
            .ToList();

        foreach (var e in q)
        {
            Console.WriteLine(e);
        }
    }

    private static void Ex4Task2_ConvertToParallelExtensions()
    {
        var q = employeeData.AsParallel()
            .Where(x => x.EmployeeID % 2 == 0).OrderBy(x => x.EmployeeID)
            .Select(x => PayrollServices.GetEmployeeInfo(x))
            .ToList();

        foreach (var e in q)
        {
            Console.WriteLine(e);
        }
    }

    private static void Ex4Task3_PLINQComprehensionSyntax()
    {
        var q = from e in employeeData.AsParallel()
                where e.EmployeeID % 2 == 0
                orderby e.EmployeeID
                select PayrollServices.GetEmployeeInfo(e);

        foreach (var e in q)
        {
            Console.WriteLine(e);
        }
    }
}

csc /r:EmployeeList.dll Program.cs outputs:

Payroll process started at 4/12/2010 10:43:45 PM
Executing GetPayrollDeduction for employee 12
Executing GetPayrollDeduction for employee 2
Executing GetPayrollDeduction for employee 14
Executing GetPayrollDeduction for employee 4
Executing GetPayrollDeduction for employee 6
Executing GetPayrollDeduction for employee 16
Executing GetPayrollDeduction for employee 8
Executing GetPayrollDeduction for employee 18
Executing GetPayrollDeduction for employee 20
Executing GetPayrollDeduction for employee 10
2 Barr, Adam
4 Bishop, Scott
6 Campbell, David
8 Clayton, Jane
10 Con, Aaron
12 Howard, Joe
14 Lang, Eric
16 McCormick, Nikki
18 MIctchell, Linda
20 Smith, Jeff
Payroll finished at 4/12/2010 10:44:11 PM and took 25.4169016

Because this article began with four major categories, I’ve only basically, in my limited knowledge, covered the TPL. There is a plethora more to the Parallel Framework, and technical documentation insists that learning these concepts is vital.

References

  • Patterns of Parallel Programming by Stephen Toub
  • The MSDN Library
  • Concurrent Programming on Windows, by Joe Duffy

History

  • 12th April, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)