本文目录
- 我怎样才能等到Parallel.ForEach完成
- 简述parallel帮助器类有哪些
- 何时使用 Parallel.ForEach,何时使用 PLINQ
- junit 测试java8的并行,出现随机的不通过
- 请问如何:编写简单的 Parallel.ForEach 循环
- C# 带泛型的Parallel.For和Foreach怎么用
- parallel.foreach 有什么 风险
- .NET的并行计算
我怎样才能等到Parallel.ForEach完成
当需要为多核机器进行优化的时候,最好先检查下你的程序是否有处理能够分割开来进行并行处理。(例如,有一个巨大的数据集合,其中的元素需要一个一个进行彼此独立的耗时计算)。
.net framework 4 中提供了 Parallel.ForEach 和 PLINQ 来帮助我们进行并行处理,本文探讨这两者的差别及适用的场景。
Parallel.ForEach
Parallel.ForEach 是 foreach 的多线程实现,他们都能对 IEnumerable《T》 类型对象进行遍历,Parallel.ForEach 的特殊之处在于它使用多线程来执行循环体内的代码段。
Parallel.ForEach 最常用的形式如下:
public static ParallelLoopResult ForEach《TSource》(
IEnumerable《TSource》 source,
Action《TSource》 body)
PLINQ
PLINQ 也是一种对数据进行并行处理的编程模型,它通过 LINQ 的语法来实现类似 Parallel.ForEach 的多线程并行处理。
场景一:简单数据 之 独立操作的并行处理(使用 Parallel.ForEach)
示例代码:
public static void IndependentAction(IEnumerable《T》 source, Action《T》 action)
{
Parallel.ForEach(source, element =》 action(element));
}
理由:
虽然 PLINQ 也提供了一个类似的 ForAll 接口,但它对于简单的独立操作太重量化了。
2. 使用 Parallel.ForEach 你还能够设定
ParallelOptions.MaxDegreeOfParalelism 参数(指定最多需要多少个线程),这样当 ThreadPool
资源匮乏(甚至当可用线程数《MaxDegreeOfParalelism)的时候, Parallel.ForEach
依然能够顺利运行,并且当后续有更多可用线程出现时,Parallel.ForEach 也能及时地利用这些线程。PLINQ
只能通过WithDegreeOfParallelism 方法来要求固定的线程数,即:要求了几个就是几个,不会多也不会少。
场景二:顺序数据 之 并行处理(使用 PLINQ 来维持数据顺序)
当输出的数据序列需要保持原始的顺序时采用 PLINQ 的 AsOrdered 方法非常简单高效。
示例代码:
public static void GrayscaleTransformation(IEnumerable《Frame》 Movie)
{
var ProcessedMovie =
Movie
.AsParallel()
.AsOrdered()
.Select(frame =》 ConvertToGrayscale(frame));
foreach (var grayscaleFrame in ProcessedMovie)
{
// Movie frames will be evaluated lazily
}
}
理由:
Parallel.ForEach 实现起来需要绕一些弯路,首先你需要使用以下的重载在方法:
public static ParallelLoopResult ForEach《TSource 》(
IEnumerable《TSource》 source,
Action《TSource, ParallelLoopState, Int64》 body)
这个重载的 Action 多包含了 index 参数,这样你在输出的时候就能利用这个值来维持原先的序列顺序。请看下面的例子: public static double PairwiseMultiply(double v1, double v2)
{
var length = Math.Min(v1.Length, v2.Lenth);
double result = new double;
Parallel.ForEach(v1, (element, loopstate, elementIndex) =》
result);
return result;
}
你可能已经意识到这里有个明显的问题:我们使用了固定长度的数组。如果传入的是 IEnumerable 那么你有4个解决方案:
(1) 调用 IEnumerable.Count() 来获取数据长度,然后用这个值实例化一个固定长度的数组,然后使用上例的代码。
(2) The second option would be to materialize the original
collection before using it; in the event that your input data set is
prohibitively large, neither of the first two options will be
feasible.(没看懂贴原文)
(3) 第三种方式是采用返回一个哈希集合的方式,这种方式下通常需要至少2倍于传入数据的内存,所以处理大数据时请慎用。
(4) 自己实现排序算法(保证传入数据与传出数据经过排序后次序一致)
2. 相比之下 PLINQ 的 AsOrdered 方法如此简单,而且该方法能处理流式的数据,从而允许传入数据是延迟实现的(lazy materialized)
场景三:流数据 之 并行处理(使用 PLINQ)
PLINQ 能输出流数据,这个特性在一下场合非常有用:
结果集不需要是一个完整的处理完毕的数组,即:任何时间点下内存中仅保持数组中的部分信息
2. 你能够在一个单线程上遍历输出结果(就好像他们已经存在/处理完了)
示例:
public static void AnalyzeStocks(IEnumerable《Stock》 Stocks)
{
var StockRiskPortfolio =
Stocks
.AsParallel()
.AsOrdered()
.Select(stock =》 new { Stock = stock, Risk = ComputeRisk(stock)})
.Where(stockRisk =》 ExpensiveRiskAnalysis(stockRisk.Risk));
foreach (var stockRisk in StockRiskPortfolio)
{
SomeStockComputation(stockRisk.Risk);
// StockRiskPortfolio will be a stream of results
}
}
这里使用一个单线程的 foreach 来对 PLINQ 的输出进行后续处理,通常情况下 foreach 不需要等待 PLINQ 处理完所有数据就能开始运作。
PLINQ 也允许指定输出缓存的方式,具体可参照 PLINQ 的 WithMergeOptions 方法,及 ParallelMergeOptions 枚举
场景四:处理两个集合(使用 PLINQ)
PLINQ 的 Zip 方法提供了同时遍历两个集合并进行结合元算的方法,并且它可以与其他查询处理操作结合,实现非常复杂的机能。
示例:
public static IEnumerable《T》 Zipping《T》(IEnumerable《T》 a, IEnumerable《T》 b)
{
return
a
.AsParallel()
.AsOrdered()
.Select(element =》 ExpensiveComputation(element))
.Zip(
b
.AsParallel()
.AsOrdered()
.Select(element =》 DifferentExpensiveComputation(element)),
(a_element, b_element) =》 Combine(a_element,b_element));
}
示例中的两个数据源能够并行处理,当双方都有一个可用元素时提供给 Zip 进行后续处理(Combine)。
Parallel.ForEach 也能实现类似的 Zip 处理:
public static IEnumerable《T》 Zipping《T》(IEnumerable《T》 a, IEnumerable《T》 b)
{
var numElements = Math.Min(a.Count(), b.Count());
var result = new T;
Parallel.ForEach(a,
(element, loopstate, index) =》
{
var a_element = ExpensiveComputation(element);
var b_element = DifferentExpensiveComputation(b.ElementAt(index));
result = Combine(a_element, b_element);
});
return result;
}
当然使用 Parallel.ForEach 后你就得自己确认是否要维持原始序列,并且要注意数组越界访问的问题。
场景五:线程局部变量
Parallel.ForEach 提供了一个线程局部变量的重载,定义如下:
public static ParallelLoopResult ForEach《TSource, TLocal》(
IEnumerable《TSource》 source,
Func《TLocal》 localInit,
Func《TSource, ParallelLoopState, TLocal,TLocal》 body,
Action《TLocal》 localFinally)
使用的示例: public static List《R》 Filtering《T,R》(IEnumerable《T》 source)
{
var results = new List《R》();
using (SemaphoreSlim sem = new SemaphoreSlim(1))
{
Parallel.ForEach(source,
() =》 new List《R》(),
(element, loopstate, localStorage) =》
{
bool filter = filterFunction(element);
if (filter)
localStorage.Add(element);
return localStorage;
},
(finalStorage) =》
{
lock(myLock)
{
results.AddRange(finalStorage)
};
});
}
return results;
}
线程局部变量有什么优势呢?请看下面的例子(一个网页抓取程序): public static void UnsafeDownloadUrls ()
{
WebClient webclient = new WebClient();
Parallel.ForEach(urls,
(url,loopstate,index) =》
{
webclient.DownloadFile(url, filenames + “.dat“);
Console.WriteLine(“{0}:{1}“, Thread.CurrentThread.ManagedThreadId, url);
});
}
通常第一版代码是这么写的,但是运行时会报错“System.NotSupportedException -》 WebClient
does not support concurrent I/O operations.”。这是因为多个线程无法同时访问同一个 WebClient
对象。所以我们会把 WebClient 对象定义到线程中来: public static void BAD_DownloadUrls ()
{
Parallel.ForEach(urls,
(url,loopstate,index) =》
{
WebClient webclient = new WebClient();
webclient.DownloadFile(url, filenames + “.dat“);
Console.WriteLine(“{0}:{1}“, Thread.CurrentThread.ManagedThreadId, url);
});
}
修改之后依然有问题,因为你的机器不是服务器,大量实例化的 WebClient 迅速达到你机器允许的虚拟连接上限数。线程局部变量可以解决这个问题: public static void downloadUrlsSafe()
{
Parallel.ForEach(urls,
() =》 new WebClient(),
(url, loopstate, index, webclient) =》
{
webclient.DownloadFile(url, filenames+“.dat“);
Console.WriteLine(“{0}:{1}“, Thread.CurrentThread.ManagedThreadId, url);
return webclient;
},
(webclient) =》 { });
}
这样的写法保证了我们能获得足够的 WebClient 实例,同时这些 WebClient 实例彼此隔离仅仅属于各自关联的线程。
虽然 PLINQ 提供了 ThreadLocal《T》 对象来实现类似的功能:
public static void downloadUrl()
{
var webclient = new ThreadLocal《WebClient》(()=》 new WebClient ());
var res =
urls
.AsParallel()
.ForAll(
url =》
{
webclient.Value.DownloadFile(url, host +“.dat“));
Console.WriteLine(“{0}:{1}“, Thread.CurrentThread.ManagedThreadId, url);
});
}
但是请注意:ThreadLocal《T》 相对而言开销更大!
场景五:退出操作 (使用 Parallel.ForEach)
Parallel.ForEach 有个重载声明如下,其中包含一个 ParallelLoopState 对象:
public static ParallelLoopResult ForEach《TSource 》(
IEnumerable《TSource》 source,
Action《TSource, ParallelLoopState》 body)
ParallelLoopState.Stop() 提供了退出循环的方法,这种方式要比其他两种方法更快。这个方法通知循环不要再启动执行新的迭代,并尽可能快的推出循环。
ParallelLoopState.IsStopped 属性可用来判定其他迭代是否调用了 Stop 方法。
示例:
public static boolean FindAny《T,T》(IEnumerable《T》 TSpace, T match) where T: IEqualityComparer《T》
{
var matchFound = false;
Parallel.ForEach(TSpace,
(curValue, loopstate) =》
{
if (curValue.Equals(match) )
{
matchFound = true;
loopstate.Stop();
}
});
return matchFound;
}
ParallelLoopState.Break() 通知循环继续执行本元素前的迭代,但不执行本元素之后的迭代。最前调用 Break
的起作用,并被记录到 ParallelLoopState.LowestBreakIteration
属性中。这种处理方式通常被应用在一个有序的查找处理中,比如你有一个排序过的数组,你想在其中查找匹配元素的最小
index,那么可以使用以下的代码:
public static int FindLowestIndex《T,T》(IEnumerable《T》 TSpace, T match) where T: IEqualityComparer《T》
{
var loopResult = Parallel.ForEach(source,
(curValue, loopState, curIndex) =》
{
if (curValue.Equals(match))
{
loopState.Break();
}
});
var matchedIndex = loopResult.LowestBreakIteration;
return matchedIndex.HasValue ? matchedIndex : -1;
}
简述parallel帮助器类有哪些
Parallel类是对线程的一个抽象。该类位于System.Threading.Tasks名称空间中,提供了数据和任务并行性。Paraller类定义了数据并行地For和ForEach的静态方法,以及任务并行的Invoke的静态方法。Parallel.For()和Parallel.ForEach()方法在每次迭代中调用相同的代码,Paraller.Invoke()允许调用不同的方法。
何时使用 Parallel.ForEach,何时使用 PLINQ
Parallel.ForEachParallel.ForEach 是 foreach 的多线程实现,他们都能对 IEnumerable《T》 类型对象进行遍历,Parallel.ForEach 的特殊之处在于它使用多线程来执行循环体内的代码段。Parallel.ForEach 最常用的形式如下: view plain copy public static void IndependentAction(IEnumerable《T》 source, Action《T》 action) { Parallel.ForEach(source, element =》 action(element)); } 理由:1. 虽然 PLINQ 也提供了一个类似的 ForAll 接口,但它对于简单的独立操作太重量化了。2. 使用 Parallel.ForEach 你还能够设定 ParallelOptions.MaxDegreeOfParalelism 参数(指定最多需要多少个线程),这样当 ThreadPool 资源匮乏(甚至当可用线程数《MaxDegreeOfParalelism)的时候, Parallel.ForEach 依然能够顺利运行,并且当后续有更多可用线程出现时,Parallel.ForEach 也能及时地利用这些线程。PLINQ 只能通过WithDegreeOfParallelism 方法来要求固定的线程数,即:要求了几个就是几个,不会多也不会少。
junit 测试java8的并行,出现随机的不通过
ArrayList本身就是线程不安全的,请用Collections.synchronizedList来获取安全的List
看以下测试
test1()方法是安全的,结果必然为26个。
test2()方法是不安全的,结果必然为不可预知,可能为24,25,26,可能有null值,可能报错。
import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.List;public class Test { public static void main(String args) { test1(); test2(); } private static void test1() { List《String》 cvals = Arrays.asList( “abcdefghijklmnopqrstuvwxyz“.split(““)); List《String》 evals = Collections.synchronizedList( new ArrayList《》()); cvals.stream().parallel().forEach((String c) -》 { evals.add(c); }); System.out.println(evals + ““); }}请问如何:编写简单的 Parallel.ForEach 循环
示例 Visual Basic ’ How to: Write a Simple Parallel.ForEach Loop ’ IMPORTANT!!!: Add reference to System.Drawing.dll Imports System.Threading Imports System.Threading.Tasks Imports System.Drawing Module ForEachDemo Sub Main() ’ A simple source for demonstration purposes. Modify this path as necessary. Dim files AsString() = System.IO.Directory.GetFiles(“C:\Users\Public\Pictures\Sample Pictures“, “*.jpg“) Dim newDir AsString = “C:\Users\Public\Pictures\Sample Pictures\Modified“ System.IO.Directory.CreateDirectory(newDir) ’ Method signature: Parallel.ForEach(IEnumerable《TSource》 source, Action《TSource》 body) ’ Be sure to add a reference to System.Drawing.dll. Parallel.ForEach(files, Sub(currentFile) ’ The more computational work you do here, the greater ’ the speedup compared to a sequential foreach loop. Dim filename AsString = System.IO.Path.GetFileName(currentFile) Dim bitmap AsNew System.Drawing.Bitmap(currentFile) bitmap.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone) bitmap.Save(System.IO.Path.Combine(newDir, filename)) ’ Peek behind the scenes to see how work is parallelized. ’ But be aware: Thread contention for the Console slows down parallel loops!!! Console.WriteLine(“Processing {0} on thread {1}“, filename, Thread.CurrentThread.ManagedThreadId) ’close lambda expression and method invocation EndSub) ’ Keep the console window open in debug mode. Console.WriteLine(“Processing complete. Press any key to exit.“) Console.ReadKey() EndSubEndModule C# namespace ForEachDemo { using System; using System.Drawing; // requires system.Drawing.dll using System.IO; using System.Threading; using System.Threading.Tasks; class SimpleForEach { staticvoid Main() { // A simple source for demonstration purposes. Modify this path as necessary.string files = System.IO.Directory.GetFiles(@“C:\Users\Public\Pictures\Sample Pictures“, “*.jpg“); string newDir = @“C:\Users\Public\Pictures\Sample Pictures\Modified“; System.IO.Directory.CreateDirectory(newDir); // Method signature: Parallel.ForEach(IEnumerable《TSource》 source, Action《TSource》 body) Parallel.ForEach(files, currentFile =》 { // The more computational work you do here, the greater // the speedup compared to a sequential foreach loop.string filename = System.IO.Path.GetFileName(currentFile); System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(currentFile); bitmap.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone); bitmap.Save(System.IO.Path.Combine(newDir, filename)); // Peek behind the scenes to see how work is parallelized.// But be aware: Thread contention for the Console slows down parallel loops!!! Console.WriteLine(“Processing {0} on thread {1}“, filename, Thread.CurrentThread.ManagedThreadId); } //close lambda expression ); //close method invocation // Keep the console window open in debug mode. Console.WriteLine(“Processing complete. Press any key to exit.“); Console.ReadKey(); } } } ForEach 循环的工作方式类似于 For 循环。根据系统环境,对源集合进行分区,并在多个线程上计划工作。系统中的处理器越多,并行方法的运行速度越快。对于某些源集合,顺序循环可能更快,具体取决于源的大小和正在执行的工作类型。有关性能的更多信息,请参见数据并行和任务并行中的潜在缺陷有关并行循环的更多信息,请参见如何:编写简单的 Parallel.For 循环若要将 ForEach 用于非泛型集合,可以使用 Cast《(Of 《(TResult》)》) 扩展方法将集合转换为泛型集合,如下面的示例所示: Visual Basic Parallel.ForEach(nonGenericCollection.Cast(OfObject), _ Sub(currentElement) ’ ... work with currentElement EndSub) C# Parallel.ForEach(nonGenericCollection.Cast《object》(), currentElement =》 { }); 还可以使用并行 LINQ (PLINQ) 来并行处理 IEnumerable《(Of 《(T》)》) 数据源。PLINQ 使您可以使用声明性的查询语法表达循环行为。
C# 带泛型的Parallel.For和Foreach怎么用
using System;using System.Collections;using System.Collections.Generic;class Program{ static void Main(string args) { Student s; LinkedList《Student》 list = new LinkedList《Student》(); LinkedListNode《Student》 node; s = new Student(12); node = new LinkedListNode《Student》(s); list.AddLast(node); s = new Student(23); node = new LinkedListNode《Student》(s); list.AddLast(node); s = new Student(34); node = new LinkedListNode《Student》(s); list.AddLast(node); s = new Student(45); node = new LinkedListNode《Student》(s); list.AddLast(node);
parallel.foreach 有什么 风险
当需要为多核机器进行优化的时候,最好先检查下你的程序是否有处理能够分割开来进行并行处理。(例如,有一个巨大的数据集合,其中的元素需要一个一个进行彼此独立的耗时计算)。.net framework 4 中提供了 Parallel.ForEach 和 PLINQ 来帮助我们进行并行处理,本文探讨这两者的差别及适用的场景。Parallel.ForEachParallel.ForEach 是 foreach 的多线程实现,他们都能对 IEnumerable《T》 类型对象进行遍历,Parallel.ForEach 的特殊之处在于它使用多线程来执行循环体内的代码段。Parallel.ForEach 最常用的形式如下:public static ParallelLoopResult ForEach《TSource》(IEnumerable《TSource》 source,Action《TSource》 body)PLINQPLINQ 也是一种对数据进行并行处理的编程模型,它通过 LINQ 的语法来实现类似 Parallel.ForEach 的多线程并行处理。场景一:简单数据 之 独立操作的并行处理(使用 Parallel.ForEach)示例代码:public static void IndependentAction(IEnumerable《T》 source, Action《T》 action){Parallel.ForEach(source, element =》 action(element));}理由: 虽然 PLINQ 也提供了一个类似的 ForAll 接口,但它对于简单的独立操作太重量化了。2. 使用 Parallel.ForEach 你还能够设定 ParallelOptions.MaxDegreeOfParalelism 参数(指定最多需要多少个线程),这样当 ThreadPool 资源匮乏(甚至当可用线程数《MaxDegreeOfParalelism)的时候, Parallel.ForEach 依然能够顺利运行,并且当后续有更多可用线程出现时,Parallel.ForEach 也能及时地利用这些线程。PLINQ 只能通过WithDegreeOfParallelism 方法来要求固定的线程数,即:要求了几个就是几个,不会多也不会少。《/ol》场景二:顺序数据 之 并行处理(使用 PLINQ 来维持数据顺序)当输出的数据序列需要保持原始的顺序时采用 PLINQ 的 AsOrdered 方法非常简单高效。示例代码:public static void GrayscaleTransformation(IEnumerable《Frame》 Movie){var ProcessedMovie =Movie.AsParallel().AsOrdered().Select(frame =》 ConvertToGrayscale(frame));foreach (var grayscaleFrame in ProcessedMovie){// Movie frames will be evaluated lazily}}理由: Parallel.ForEach 实现起来需要绕一些弯路,首先你需要使用以下的重载在方法:public static ParallelLoopResult ForEach《TSource 》(IEnumerable《TSource》 source,Action《TSource, ParallelLoopState, Int64》 body)《/ol》这个重载的 Action 多包含了 index 参数,这样你在输出的时候就能利用这个值来维持原先的序列顺序。请看下面的例子: public static double PairwiseMultiply(double v1, double v2){var length = Math.Min(v1.Length, v2.Lenth);double result = new double +“.dat“));Console.WriteLine(“{0}:{1}“, Thread.CurrentThread.ManagedThreadId, url);});}但是请注意:ThreadLocal《T》 相对而言开销更大! 场景五:退出操作 (使用 Parallel.ForEach)Parallel.ForEach 有个重载声明如下,其中包含一个 ParallelLoopState 对象:public static ParallelLoopResult ForEach《TSource 》(IEnumerable《TSource》 source,Action《TSource, ParallelLoopState》 body)ParallelLoopState.Stop() 提供了退出循环的方法,这种方式要比其他两种方法更快。这个方法通知循环不要再启动执行新的迭代,并尽可能快的推出循环。ParallelLoopState.IsStopped 属性可用来判定其他迭代是否调用了 Stop 方法。示例:public static boolean FindAny《T,T》(IEnumerable《T》 TSpace, T match) where T: IEqualityComparer《T》{var matchFound = false;Parallel.ForEach(TSpace,(curValue, loopstate) =》{if (curValue.Equals(match) ){matchFound = true;loopstate.Stop();}});return matchFound;}ParallelLoopState.Break() 通知循环继续执行本元素前的迭代,但不执行本元素之后的迭代。最前调用 Break 的起作用,并被记录到 ParallelLoopState.LowestBreakIteration 属性中。这种处理方式通常被应用在一个有序的查找处理中,比如你有一个排序过的数组,你想在其中查找匹配元素的最小 index,那么可以使用以下的代码:public static int FindLowestIndex《T,T》(IEnumerable《T》 TSpace, T match) where T: IEqualityComparer《T》{var loopResult = Parallel.ForEach(source,(curValue, loopState, curIndex) =》{if (curValue.Equals(match)){loopState.Break();}});var matchedIndex = loopResult.LowestBreakIteration;return matchedIndex.HasValue ? matchedIndex : -1;}
.NET的并行计算
并行计算的出现,是计算机科学发展的必然结果,随着计算机硬件的迅猛发展,在多核处理器上工作已经是既存事实,而传统的编程模式必须兼容新的硬件环境才能使计算机性能达到合理的应用效果。用Anders大师的话说:未来5到10年,并行计算将成为主流编程语言不可忽视的方向,而4.0为C#打响了实现并发的第一枪。未来的.NET Framework 4.0中将集成TPL(Task Parallel Library)和PLINQ(Parallel LINQ),这也意味着未来我们可以应用C# 4.0实现并行化应用,在统一的工作调度程序下进行硬件的并行协调,这将大大提高应用程序的性能同时降低现存并发模型的复杂性。那么,我们应该一睹为快应用C#武器来开发并发环境下的超酷感受,在System.Threading.Parallel 静态类提供了三个重要的方法For、Foreach、Invoke可以为我们小试牛刀://应用TPL,执行并行循环任务Parallel.For(0,10,i =》{DoSomething(i);});在线程争用执行情况下,相同的操作在双核平台下运行,以StopWatch进行精确时间测试,并行环境下的执行时间为 2001ms,而非并行环境下的执行时间为4500ms,并行运算的魅力果然名不虚传。我们再接再厉应用PLINQ执行对于并行运算的查询、排序等,当前PLINQ支持两种方式ParallelEnumerable类和ParallelQuery类,例如:int data = new int { 0,1,2,3,4,5,6,7,8,9 };int selected = (from x in data.AsParallel()select x + 1).ToArray();更详细的对比示例留待读者在实践中对此进行讨论,并行计算为托管代码在多核环境下的性能优化提供了统一的解决方案,而未来我们会做的更好。备注:实际上,我们可以选择下载安装Microsoft Parallel Extensions to the .NET Framework 3.5June 2008 CTP包,就可以在.NET 3.5环境下体验并行计算的无穷魅力。