65.9K
CodeProject 正在变化。 阅读更多。
Home

并行扩展至 .NET 4.0 运行时

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.54/5 (10投票s)

2010年4月12日

CPOL

5分钟阅读

viewsIcon

27565

本文主要关注 TPL。

总述

本文将主要关注任务并行库 (Task Parallel Library) - 它的用法及其在并行编程中的地位。为了避免遗漏一些必要的概念,本文还将(简要)提及 PLINQ 和 Parallel 类的 For 方法。微软在其最新的 .NET 4.0 运行时版本中加入了一些非常必要的改进,以跟上多核处理器技术的发展。程序员不仅面临着技术上的微小变化,最终还将不得不适应并行编程中的一些模式。这些新增内容可以分为四大类:

  1. 一组面向任务的 API,称为任务并行库 (TPL)。提供了一个任务对象模型,用于思考并行编程,此外还有一些辅助类,用于执行常见的命令式(非声明式)数据并行操作,例如并行 For 循环。这些结构和多线程 API,以及 Parallel 类,共同构成了并行框架 (PFX)。
  2. .NET LINQ 的数据并行实现。Parallel LINQ (PLINQ) 查询提供程序通过 TPL,间接自动并行化对内存数据结构的任何 LINQ-to-Objects 查询。
  3. 一套完整的同步原语,封装了常见的协调事件。
  4. 一组并发集合

任务并行库

TPL 中的并发单元是 Task 对象。此类提供了大部分功能,并且像大多数其他 TPL 类一样,可以在 System.Threading.Tasks 命名空间中找到。

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

Task 的首要方面是构造函数和静态 StartNew 工厂方法。两者都提供相同的重载;StartNew 方法实际上是构造新任务并立即调用其 Start 方法的常见操作的快捷方式。下面的代码示例直接引用自 MSDN 库。创建 Task 对象后,Start() 会开始任务调度过程。但任何读者可能都在想 Task 是如何创建的。一旦一切准备就绪可以并行化,组成线程的代码指令集就会被分配给任务。当 Task 返回结果时,结果是以 Task 的形式出现的,而不是 Action 委托。操作会一直阻塞,直到 Task 完成。任务可以链接在一起,因此一个 Task 的返回是另一个 Task。本文将包含原始内容,但此代码示例之后将附带解释,以帮助阐明其工作原理和原因。

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

输出

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

好的。现在注意到

  • 每一个新任务都需要提供一个 Action。这是一个在任务实际运行时将执行的委托。
  • 可以选择提供一个对象状态参数。这适用于那些接受 Action<object> 的重载,而该值当然会作为整体传递给委托。回想一下,通过委托进行的方法调用将方法调用的定义和调用分开为两部分。
  • 可以提供一个 TaskManager 对象。

当您想在线程池线程上运行某些操作,并通过 continuation 和父/子任务来管理任务的工作流时,任务并行构造非常有用。当您想并行执行操作然后等待它们完成(结构化并行)时,PLINQ 和 Parallel 类非常有用。这包括非 CPU 密集型任务,例如调用 Web 服务。

Parallel For 和 Parallel ForEach

Parallel.For 和 Parallel.ForEach 执行与 C# 的 for 和 foreach 循环等效的操作,但每个迭代都并行执行,而不是顺序执行。这是它们(最简单)的签名:

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

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

我们的方法实现将针对范围 [inclusiveLowerBound,exclusiveUpperBound) 中的每个元素调用循环体一次。以下顺序循环

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

将被这样并行化:

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

首先,我们需要一个签名。为了并行化 for 循环,我们将实现一个接受三个参数的方法:一个下界,一个上界,以及一个接受一个整数参数(代表当前迭代索引)的循环体委托(该委托将为每次迭代调用一次)。请注意迭代空间的定义。现在简要看一下这个 PLINQ 示例:

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

这会输出到:

 
  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

并行 LINQ

LINQ 允许我们通过对 System.Linq.Enumerable 类的 API 调用系列,或者通过使用 C# 等语言支持的语言集成查询语法来编写声明式(而非命令式)查询。枚举器的通用概念是一个类型,其唯一目的是逐个读取并遍历另一个集合的内容。通常没有写入功能。PLINQ 通过分析查询,并安排将查询的不同部分在多个处理器上并行运行。它最终是通过在底层使用 TPL 来实现的。下面的示例引用了一个 Employee 类。实际程序包含一系列私有的静态方法,所有这些方法都用于举例说明并行构造、PLINQ、For 方法循环等的用法。请注意,此类是为了传递给另一个类等而编写的。

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 输出 EmployeeList.dll

现在这是外部引用该 DLL 的代码:

    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 输出:

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

由于本文开头提到了四个主要类别,而我以我有限的知识,基本上只涵盖了 TPL。并行框架还有很多内容,技术文档坚持认为学习这些概念至关重要。

参考文献

  • Stephen Toub 的《并行编程模式》
  • MSDN 库
  • Joe Duffy 的《Windows 并发编程》

历史

  • 2010 年 4 月 12 日:首次发布
© . All rights reserved.