zoukankan      html  css  js  c++  java
  • 在.NET下使用Task Parallel Library提高程序性能

    .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。下图大致描述了这个处理过程:

    image

    现在让我们先定义这个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中的值是否有序。

    image

    以下是传统实现方式下,CPU的利用率。我们可以看到,基本上CPU的利用率只能达到20%-30%左右,大部分CPU资源都没有利用到:

    image

    以下是基于TPL方式下,CPU的利用率,基本上能达到85%以上(估计剩下的部分由于IO的原因,所以没有达到更高的CPU利用率):

    image

    参考书籍

    案例代码下载

    【请单击此处下载本文案例源代码】


    前文回顾:

    在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文件替换掉即可。例如我使用下面的图标作为模板的图标:

    image

    现在编译CMSProjectTemplate项目,并将产生的ZIP文件拷贝到Visual C#的ProjectTemplate目录下,重新打开New Project对话框,我们可以看到下面的效果:

    image

    创建Visual Studio 2010扩展的安装包VSIX文件

    现在,我们可以使用VSIX来为最终用户提供一个安装项目模板的安装包,到时候用户只需要双击这个VSIX文件即可将所需的项目模板以插件的形式安装到Visual Studio中。

    首先,在CMSProjectTemplate解决方案中,新建一个VSIX Project的项目,我们取名为CMSProjectTemplateVSIX:

    image

    在source.extension.vsixmanifest文件的设计界面,设置如下属性:

    • Product Name:Customer Management System Project Template
    • Author:<填写你自己的姓名,或者公司名>
    • Description:<填写一些描述信息>

    其它内容你可以选填,至于License Terms,你可以找一个txt或者rtf文件,用来描述许可协议。填写完后,设计界面大致如下:

    image

    然后,在设计界面的Content部分,单击Add Content按钮,此时将弹出Add Content对话框,在Select a content type下拉框中,选择Project Template,在Select a source选项中选择CMSProjectTemplate项目,然后单击OK按钮:

    image

    用相同的方法,添加Template Wizard:

    image

    完成这两项内容的添加以后,设计界面的Content部分大致如下:

    image

    OK,现在保存并编译CMSProjectTemplateVSIX项目,完成编译之后,我们在输出目录中找到了VSIX文件:

    image

    双击CMSProjectTemplateVSIX.vsix文件,将出现如下对话框:

    image

    单击Install按钮完成Visual Studio 2010扩展的安装。安装完成后,重新启动Visual Studio 2010,点击Tools –> Extension Manager菜单,我们可以在打开的Extension Manager对话框中找到刚刚安装的扩展包:

    image

    用户可以根据自己的需要对其进行禁用或者卸载。

    总结

    本系列文章从一个案例解决方案开始,逐步介绍了如何使用Visual Studio 2010 SDK来创建一个多项目的解决方案模板项目,并介绍了其中的一些高级应用。希望这样的文章能够真正地帮助到有这方面需求的读者朋友。

    本文案例下载

    CMSProjectTemplate(完整版)

    参考文献

    摘要: 前文回顾:在Visual Studio 2010中创建多项目(解决方案)模板【一】:多项目解决方案模板的创建在Visual Studio 2010中创建多项目(解决方案)模板【二】:Template Wizard的使用本文主要讨论多项目(解决方案)模板的部署相关问题,包括:为多项目解决方案模板设置模板名称修改多项目解决方案模板的图标创建Visual Studio 2010扩展的安装包VSIX文件为多项目解决方案模板设置模板名称模板名称的设置非常简单,,只需要修改CMSProjectTemplate.vstemplate文件中的Name XML节点的内容即可。例如,我们可以为我们的模板起名为:C阅读全文

    posted @ 2012-01-19 14:43 dax.net 阅读(1301) | 评论 (0) 编辑 |

    摘要: 在上文中我给大家介绍了多项目解决方案模板的创建,在文章的最后我们遇到了一个问题,就是$safeprojectname$这个模板参数(宏)所指代的意义在各个项目中都不一样,而我们却希望它能够简单地指代用户所输入的项目名称。本文将从这个问题出发,讨论在Visual Studio 2010中是如何使用Template Wizard来设计复杂的多项目解决方案的。Template Wizard的基本应用创建Template Wizard项目在CMSProjectTemplate解决方案下,新建一个C# Class Library,取名为CMSProjectTemplateWizard,在该项目上添加Mi阅读全文

    posted @ 2012-01-18 20:17 dax.net 阅读(1277) | 评论 (1) 编辑 |

    摘要: 当我们使用Visual Studio来新建某个项目(Project)时,通常都会使用File –> New –> Project菜单来打开New Project(新建项目)对话框,里面列出了各种项目类型以供我们选择。大部分读者朋友都应该知道,这个对话框其实是列出了所有已经安装的项目模板,不仅如此,Visual Studio还允许用户通过File –> Export Template菜单将现有的项目导出为项目模板。平时我们最为常见的是使用Export Template来创建单一项目的项目模板,此时使用Export Template功能就十分有效。当然,社区里也有一些工具(比如微阅读全文

    posted @ 2012-01-17 19:21 dax.net 阅读(1712) | 评论 (9) 编辑 |


  • 相关阅读:
    Reactor系列(四)subscribe订阅
    Reactor系列(三)创建Flux,Mono(续)
    Reactor系列(二)Flux Mono创建
    Reactor系列(一)基本概念
    Stream系列(十五)File方法使用
    Stream系列(十四)parallet方法使用
    OpenCV二值化、归一化操作
    C# 队列
    linux shell脚本程序路径作为变量
    C++中头文件(.h)和源文件(.cpp)都应该写些什么
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2355951.html
Copyright © 2011-2022 走看看