.NET 4.0中的Task Parallel Library(TPL)已经不是什么新鲜事了,相信很多朋友也阅读过不少有关TPL的书籍资料。而另一方面,能够将TPL合理地运用在实际项目开发过程中,以提高程序的执行效率,这种情况也并不多见。本文就以实际项目中的一个程序功能为例,简要讨论一下TPL的应用。在此我不打算对TPL的相关基础知识做过多讨论,这些内容在网上应该有不少的文章资料可供参考;同时读者朋友还可以阅读一些有关TPL的经典书籍,以便加深对TPL的理解。文章最后我会推荐几本不错的有关.NET 4.0下TPL的书籍资料。
案例:批量对象的XML序列化
在某个项目中,需要对一大批相同类型的对象进行XML序列化操作,在序列化工作完成后,程序会把序列化所得的XML字符串根据对象的ID值保存到一个字典(Dictionary)的对象中,以便后续的程序逻辑能够使用这些序列化后的XML。为了简化起见,我定义了一个Customer类来模拟这些对象的类型(实际项目中的对象类型要比这个Customer复杂一些),这个Customer类仅包含两个属性:ID和Name。下图大致描述了这个处理过程:
现在让我们先定义这个Customer类,以便为接下来的实验作准备。Customer类的定义如下:
1
2
3
4
5
6
7
8
9
10
|
public class Customer { public long ID { get ; set ; } public string Name { get ; set ; } public override string ToString() { return Name; } } |
下面,我们分别使用传统的方式和基于TPL的并行处理方式来实现这个程序,然后比较一下这两种方式产生的效果差异。
传统的实现方式
传统的实现方式很简单,基本思路就是对每一个Customer对象,使用XmlSerializer对其进行序列化操作,然后把产生的XML字符串保存到字典中。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
static IEnumerable<KeyValuePair< long , string >> SerializeCustomers(Customer[] customers) { var dict = new Dictionary< long , string >(); var xmlSerializer = new XmlSerializer( typeof (Customer)); foreach (var customer in customers) { using (var ms = new MemoryStream()) { xmlSerializer.Serialize(ms, customer); dict.Add(customer.ID, Encoding.ASCII.GetString(ms.ToArray())); } } return dict; } |
基于TPL的并行处理方式
在采用这种方式之前,需要对我们的应用场景进行分析。今后在项目中打算使用TPL之前,都应该进行这样的分析。主要目的就是为了讨论目前我们所面对的场景,是否可以使用并行计算。目前我们的应用场景是可以采用TPL的并行处理方式的。因为首先,针对每个Customer对象的序列化操作都相对独立,没有先后顺序之分,即各操作之间是可替换的,比如计算a+b+c,可以先计算a+b(也就是(a+b)+c),也可以先计算b+c(也就是a+(b+c));其次,虽然在最后整合结果的时候需要访问跨线程的共享资源,也就是在最后整合结果的时候产生了资源的依赖关系,但对于整个计算的过程,各个任务都是可以互不干扰地执行的。在运用TPL的时候,我觉得应该尽可能地降低各个任务之间的依赖关系,因为TPL中的任务有可能会被分配到不同的线程去执行,如果任务之间有资源的相互依赖的话,线程同步将降低任务执行的效率。
以下是此案例的TPL版本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
static IEnumerable<KeyValuePair< long , string >> ParallelSerializeCustomers(Customer[] customers) { var dict = new Dictionary< long , string >(); var xmlSerializer = new XmlSerializer( typeof (Customer)); object lockObj = new object (); Parallel.ForEach(customers, () => new Dictionary< long , string >(), (customer, loopState, single) => { using (var ms = new MemoryStream()) { xmlSerializer.Serialize(ms, customer); single.Add(customer.ID, Encoding.ASCII.GetString(ms.ToArray())); } return single; }, (single) => { lock (lockObj) { single.ToList().ForEach(p => dict.Add(p.Key, p.Value)); } }); return dict; } |
在ParallelSerializeCustomers方法中,采用了foreach循环的并行版本:Parallel.ForEach方法。这个方法与foreach类似,会逐个轮询给定的IEnumerable对象中的没一个值,不过Parallel.ForEach方法会将这个轮询的过程分配到多个Task上执行,因此对于Parallel.ForEach,执行过程的中断(break)以及异常处理都与foreach完全不同。在这个例子中,我们使用的是Parallel.ForEach方法的其中一个重载版本,在这个方法重载中,首先我们将需要轮询的IEnumerable对象(也就是这里的customers数组)传递给该方法;之后有一个Func<TLocal>的委托参数,这个委托参数的作用是为了对Task执行线程范围内的局部变量进行初始化,在这里我们直接使用Lambda表达式返回了一个新建的Dictionary<long, string>对象,表示需要对线程范围内的局部变量(其实就是第三个参数中的那个single变量)初始化成一个新的Dictionary<long, string>实例;第三个参数也是一个委托,用于对当前的枚举对象执行真正的处理逻辑,然后将处理结果返回;第四个参数则是用来整合每个任务的处理结果,以得到最终结果。不难看出,在整合最终结果的时候,多个线程需要同时访问dict变量,因此需要使用lock关键字以保证线程同步。
执行效果对比
以下是在一台具有4核CPU的计算机上,处理十万(100000)个Customer对象的执行效果,可见基于TPL的实现效率要比传统的实现方式高很多。值得一提的是,传统方式所产生的dict是有序的,而基于TPL的方式所产生的dict则是无序的,但这并不影响结果,因为程序并不会关心dict中的值是否有序。
以下是传统实现方式下,CPU的利用率。我们可以看到,基本上CPU的利用率只能达到20%-30%左右,大部分CPU资源都没有利用到:
以下是基于TPL方式下,CPU的利用率,基本上能达到85%以上(估计剩下的部分由于IO的原因,所以没有达到更高的CPU利用率):
参考书籍
- 《Parallel Programming with Microsoft Visual Studio 2010 Step by Step》
- 《Professional Parallel Programming with C#: Master Parallel Extensions with .NET 4》
- 《Parallel Programming with Microsoft .NET: Design Patterns for Decomposition and Coordination on Multicore Architectures》
案例代码下载
前文回顾:
在Visual Studio 2010中创建多项目(解决方案)模板【一】:多项目解决方案模板的创建
在Visual Studio 2010中创建多项目(解决方案)模板【二】:Template Wizard的使用
本文主要讨论多项目(解决方案)模板的部署相关问题,包括:
- 为多项目解决方案模板设置模板名称
- 修改多项目解决方案模板的图标
- 创建Visual Studio 2010扩展的安装包VSIX文件
为多项目解决方案模板设置模板名称
模板名称的设置非常简单,,只需要修改CMSProjectTemplate.vstemplate文件中的Name XML节点的内容即可。例如,我们可以为我们的模板起名为:Customer Management System Solution:
1
|
<Name>Customer Management System Solution</Name> |
修改多项目解决方案模板的图标
模板图标的修改也非常简单,在文件系统中找一个ICO的图标文件,将CMSProjectTemplate项目目录下的CMSProjectTemplate.ico文件替换掉即可。例如我使用下面的图标作为模板的图标:
现在编译CMSProjectTemplate项目,并将产生的ZIP文件拷贝到Visual C#的ProjectTemplate目录下,重新打开New Project对话框,我们可以看到下面的效果:
创建Visual Studio 2010扩展的安装包VSIX文件
现在,我们可以使用VSIX来为最终用户提供一个安装项目模板的安装包,到时候用户只需要双击这个VSIX文件即可将所需的项目模板以插件的形式安装到Visual Studio中。
首先,在CMSProjectTemplate解决方案中,新建一个VSIX Project的项目,我们取名为CMSProjectTemplateVSIX:
在source.extension.vsixmanifest文件的设计界面,设置如下属性:
- Product Name:Customer Management System Project Template
- Author:<填写你自己的姓名,或者公司名>
- Description:<填写一些描述信息>
其它内容你可以选填,至于License Terms,你可以找一个txt或者rtf文件,用来描述许可协议。填写完后,设计界面大致如下:
然后,在设计界面的Content部分,单击Add Content按钮,此时将弹出Add Content对话框,在Select a content type下拉框中,选择Project Template,在Select a source选项中选择CMSProjectTemplate项目,然后单击OK按钮:
用相同的方法,添加Template Wizard:
完成这两项内容的添加以后,设计界面的Content部分大致如下:
OK,现在保存并编译CMSProjectTemplateVSIX项目,完成编译之后,我们在输出目录中找到了VSIX文件:
双击CMSProjectTemplateVSIX.vsix文件,将出现如下对话框:
单击Install按钮完成Visual Studio 2010扩展的安装。安装完成后,重新启动Visual Studio 2010,点击Tools –> Extension Manager菜单,我们可以在打开的Extension Manager对话框中找到刚刚安装的扩展包:
用户可以根据自己的需要对其进行禁用或者卸载。
总结
本系列文章从一个案例解决方案开始,逐步介绍了如何使用Visual Studio 2010 SDK来创建一个多项目的解决方案模板项目,并介绍了其中的一些高级应用。希望这样的文章能够真正地帮助到有这方面需求的读者朋友。
本文案例下载
CMSProjectTemplate(完整版)
参考文献
- Creating and Sharing Project & Item Templates:http://blogs.msdn.com/b/visualstudio/archive/2010/03/04/creating-and-sharing-project-item-templates.aspx
- How to: Create Multi-Project Templates: http://msdn.microsoft.com/en-us/library/ms185308.aspx
- Can't Avoid the ProjectGuid from Being Changed in .csproj File:http://social.msdn.microsoft.com/Forums/en-US/csharpide/thread/1d632940-cc1d-49d5-a64c-d3e999216cbd
- VSPackage: Force a project to unload then reload:http://social.msdn.microsoft.com/Forums/en/vsx/thread/49f69447-951a-4a9c-9c69-9a821f2a367c
- Multi-Project Templates with Wizard: Visual Studio 2010 Sample:http://vsix.codeplex.com/
posted @ 2012-01-19 14:43 dax.net 阅读(1301) | 评论 (0) 编辑 |
posted @ 2012-01-18 20:17 dax.net 阅读(1277) | 评论 (1) 编辑 |
posted @ 2012-01-17 19:21 dax.net 阅读(1712) | 评论 (9) 编辑 |