一起谈.NET技术,在.NET Workflow 3.5中使用多线程提高工作流性能

  最近在工作上碰到一个性能问题,由于项目是基于SOA的架构,使得整个系统完全依赖于各种各样的Service,其中用于处理业务逻辑的Business Services全部都用.NET Workflow 3.5实现(历史原因,项目还没升级到Workflow 4)。在众多的Business Service中,其中有一个的主要功能是,通过调用不同的Data Service来获取数据,然后根据业务逻辑来组织这些数据并返回给它的调用者。该Business Service的工作流(Workflow)主要包含三个活动组件(Activity),大致可以用下图表示:

  需要说明一下,在实际项目中,这个Workflow本身不仅仅只是简单地包含上面三个Activity,通过性能测试的数据分析,瓶颈就在这三个Activity上,而每个Activity的执行时间又主要消耗在反复调用Data Service上。在此,为了简化问题的描述,我把其它不相干的Activity剔除了,于是就得到了上图的结构。

  图中的三个Activity都会分别去调用不同的Data Service来获得数据,尤其在getNotesActivity中,Data Service会被循环调用,这使得系统性能大打折扣。原本有一个解决方案可以在一定程度上提高getNotesActivity的效率,就是修改被调用的Data Service,使得它能够一次性接收多个request的数据,处理完之后再将所有的结果一次性返回,这样就避免了Data Service的循环调用,有效地减少了数据在网络上的来回次数。但是,这种解决方案需要更改Data Service的Request Schema,这个改动是很大的,因为可能有很多其它的Business Service都在调用这个Data Service,牵涉的范围太广了。

  根据实际项目,稍加分析不难发现,这个Workflow中的Activity有如下几个特点:

  • 三个Activity的输入属性参数都来自于Workflow(即通过与Workflow中定义的DependencyProperty进行绑定而获得数据),并不存在下游的Activity的输入参数需要依赖上游Activity的输出参数的情况
  • 每个Activity的输出属性参数都只关注某一种类型的数据,在Workflow Runtime执行完某个Activity后,也会通过DependencyProperty将处理结果传递给Workflow,而这些输出属性参数之间也并没有任何关联
  • 三个Activity所调用的Data Service也比较独立,基本上可以说是在做三个完全不同的工作
  • 时间主要消耗在Data Service的调用上,不存在由于复杂的运算逻辑导致CPU利用率近似100%的情况,也不存在由于物理内存用完而需要频繁读写虚拟内存的情形

  上述的几个特点中,第四点为我们引入多线程或并行任务处理提供了主要依据。这里需要额外岔开一下。有很多软件人员认为,多线程一定能够提高系统性能,因为事务可以分派到多个线程中进行并行处理。我觉得,应该这样去看待这个问题:首先,根据Martin Fowler的《企业应用架构模式》(也就是著名的PoEAA)一书中有关性能的讨论认为,有很多术语可以描述性能,比如:响应时间、响应性、等待时间、吞吐率、负载、负载敏感度等。假设完成某个任务需要的时间很长,比如需要5秒,那么其响应时间就是5秒,而如果让用户等待这5秒过去后,再将系统的控制权交给用户,就会让用户明显感觉很不顺,于是响应性就很差;但如果能将这个任务交给另一个执行体去处理,而程序本身直接将系统控制权交给用户,等那个执行体完成任务处理后,再将结果提供给用户,那么,同样处理这个任务需要5秒钟,这种方式的响应性就明显要好于前者,这也是我们以前做Windows Forms开发的时候,要把耗时的处理交给另一个线程处理,以不至于因为主线程的阻塞而导致界面冻结的尴尬局面。因此,多线程的引入,可以提高系统的响应性。

  其次,多线程是否能够提高系统的响应时间?这也未必,在单核处理器上,多线程是采用时间片轮循的方式实现的,也就是说,相同时间点上,只有一个线程在执行,只不过是时间片足够小,轮循频率足够高,才让我们感觉线程是并行执行的,在这样一种体系结构下,完成任务的处理还是需要那么长时间,甚至时间片的切换倒还会带来额外的开销。在多核系统中,或许真的可以提高响应时间,不过我目前没有实际的测试数据用来比较,因此在这个问题上,我还没有足够的发言权。

  而对于目前项目的情况,Data Service是分布在网络上不同位置的资源,如果能让这些Data Service同时处理数据请求,再让Business Service去组织分别来自这些Data Service的处理结果,那么整个Business Service的响应时间是可以明显提高的,响应时间提高了,响应性也同样提高了。假设第一个Activity耗时t1,第二个Activity耗时t2,第三个Activity耗时t3,那么,如果按上图中的顺序方式执行,Business Service的响应时间就是t1+t2+t3。但如果让这些Activity并行处理(也就相当于并行调用Data Service使其同时处理数据请求),那么Business Service的响应时间应该就是max(t1, t2, t3)。

  于是,我打算将上述的Workflow修改一下,采用多线程的方式来分别运行每个Activity,最后再将结果汇总。我修改后的Workflow如下所示:

  在此需要对ParallelActivity说明一下。.NET Workflow 3.5的ParallelActivity并没有做到所谓的并行执行,因为Workflow Runtime是在单独的线程上执行Workflow Instance的,因此,要让多个Activity真正并行执行是做不到的。ParallelActivity的真正用意在于协调每个分支中的SequenceActivity(注意:ParallelActivity的每个分支只能接收一个SequenceActivity),使得其中的每个Activity都有一次执行的机会。

  某个分支中的一个活动执行过后,就会轮到下一个分支。当这个分支执行了一个活动后,执行又会转移到再下一个分支,以此类推。当所有分支都有了执行机会之后,又会从第一个(最左侧)分支开始这一过程,继续执行第一个分支的下一个活动(如果存在的话)。因此,在我们的这个例子中,完全可以不用ParallelActivity,而仍然选择原来的结构即可。之前我并没有完全清楚地了解ParallelActivity,开始一直以为ParallelActivity的意思是,让Workflow Runtime同时安排(Schedule)每个分支的执行,以便当每个分支都以异步方式运行时,所有的分支可以实现并行处理。

  不过也不要紧,在这里使用ParallelActivity,虽然没有有效地利用它的特性,但与原来的Workflow相比,从可读性上讲,这种结构更容易让人觉得这是一种并行的运行方式。

  另一个变化是,原本每个操作都是写在一个自定义的Activity中的,通过重写Activity的Execute方法来做业务处理,而现在则是用CodeActivity来代替原来的Activity,这样做的好处是,可以将业务处理的代码放在同一个Context中,这也为线程同步提供了便利,降低了使用线程的复杂度。

  以下是改进后的Workflow的代码,供参考。


1. using System;
2. using System.Collections.Generic;
3. using System.Threading;
4. using System.Workflow.Activities;
5. namespace WorkflowConsoleApplication3
6. {
7. public sealed partial class Workflow1 : SequentialWorkflowActivity
8. {
9. List<Thread> threads = new List<Thread>();
10. public Workflow1()
11. {
12. InitializeComponent();
13. }
14. private void getAdditionalInfoActivity_Execute(object sender, EventArgs e)
15. {
16. var t1 = new Thread(() =>
17. {
18. // Call Data Service 1 to implement business logic...
19. });
20. threads.Add(t1);
21. t1.Start();
22. }
23. private void getNotesActivity_Execute(object sender, EventArgs e)
24. {
25. var t2 = new Thread(() =>
26. {
27. // Call Data Service 2 in a loop to implement business
28. // logic...
29. });
30. threads.Add(t2);
31. t2.Start();
32. }
33.
34. private void getSpecialPointsActivity_Execute(object sender, EventArgs e)
35. {
36. var t3 = new Thread(() =>
37. {
38. // Call Data Service 3 to implement business logic...
39. });
40. threads.Add(t3);
41. t3.Start();
42. }
43.
44. private void syncCodeActivity_Execute(object sender, EventArgs e)
45. {
46. // Wait for all threads to terminate...
47. threads.ForEach(p => p.Join());
48. // TODO: Process with results and exceptions
49. }
50. }
51. }
52. 从上面的代码中可以看到,每个

  从上面的代码中可以看到,每个CodeActivity在执行的时候都会启动一个线程,这个线程会调用相应的Data Service来实现其业务逻辑,线程创建以后,会被保存在一个线程列表里,用来在syncCodeActivity中进行线程同步。syncCodeActivity则通过线程的Join方法来等待所有线程全部完成各自的工作,最后对运行结果和异常进行处理。

  此处线程的运用需要遵循.NET线程使用的最佳实践,应该尽量避免线程的阻塞,在访问临界资源的时候应作加锁处理以防止状态异常。由于在这个例子中,每个线程又会牵涉到其它Service的调用,因此在线程中捕获的异常,我建议还是先将其记录下来,然后温和地直接使用return语句终止线程执行,而不是随意抛出异常而使得线程进入一个不确定的状态。当然,读者朋友如果在多线程环境中有处理异常的经验,也恳请在本文留言指导。

  对Workflow进行调整之后,重新编译、部署并运行这个Business Service,然后用已经写好的Client程序进行测试,我们得到了如下的结果(几个明显的噪音数据已经被划掉,没有包含在统计中)。从这个报表可以看到,针对我们的这个案例,在Workflow中引入多线程的确可以明显地提高系统性能。

时间: 2024-09-15 18:06:40

一起谈.NET技术,在.NET Workflow 3.5中使用多线程提高工作流性能的相关文章

一起谈.NET技术,ASP.NET MVC 2中使用jQuery UI控件详解

问:我想给我的ASP.NET MVC输入表单添加一个日期选择控件,但模型-视图-控制器(MVC)并没有提供这样的辅助方法,我该如何添加控件? 答:和ASP.NET Web表单不一样,MVC架构没有提供可以在设计面板中拖放的有状态的服务端控件,相反,MVC鼓励使用简单的HTML布局元素和基于数据的标签作为页面布局的要素,功能和最终的布局用客户端JavaScript和CSS样式表控制. MVC提供了一套基于HtmlHelper的扩展方法渲染大部分HTML标签,对于更复杂的功能,你需要自己编写HTML

一起谈.NET技术,在ASP.NET MVC3 中利用JSONP跨域登录WEB系统

在信息系统开发的时,根据相关业务逻辑难免会多系统之间互相登录.一般情况下我们需要在多系统之间使用多个用户名和密码.这样客户就需要在多个系统之间重复登陆.每次登录都需要输入用户名和密码.最近比较流行的就是OAuth.新浪微博这个开放系统做的就很好.但OAuth并非本文讨论范畴.这里主要讨论jQuery1.5 jsonp 在Asp.net MVC3 中的应用. 本文应用场景: 假设您的开发团队欲为某集团公司开发一整套信息管理系统,目前首要开发的就是一套订单系统和一套内部OA系统.前提是这两套系统使用

一起谈.NET技术,.Net Discovery系列之-深入理解平台机制与性能影响 (中)

上一篇文章中Aicken为大家介绍了.Net平台的垃圾回收机制与其对性能的影响,这一篇中将继续为大家介绍.Net平台的另一批黑马-JIT.有关JIT的机制分析 ● 机制分析以C#为例,在C#代码运行前,一般会经过两次编译,第一阶段是C#代码向MSIL的编译,第二阶段是IL向本地代码的编译.第一阶段的编译成果是生成托管模块,第二阶段的编译成果是生成本地代码以供运行,从这里各位同学可以看出,第一阶段生成的MSIL是不能直接运行的.必须指出的是JIT在第一次编译IL后,会修改对应方法相应的内存地址入口

一起谈.NET技术,在ASP.NET MVC中进行TDD开发

TDD介绍 TDD是一种开发方法,全称是Test-Driven development,中文是测试驱动开发.作者是Kent Beck.首先让我介绍一下三种常见的开发方式: 第一种:先Coding,然后Bug Fix. 第二种:先Coding,然后Unit Test,最后Bug Fix.很显然用了单元测试的比第一种开发方式要好不少. 第三种:就是本文要说的TDD,它的方式和第二种恰恰相反.TDD先设计单元测试,然后再Coding,最后修复Bug.看下图: TDD开发过程可以看成:给制自己制定一个目

一起谈.NET技术,Asp.net mvc 2中使用Ajax的三种方式

     在Asp.net MVC中,我们能非常方便的使用Ajax.这篇文章将介绍三种Ajax使用的方式,分别为原始的Ajax调用.Jquery.Ajax Helper.分别采用这三种方式结合asp.net mvc去实现一个史上最简单的留言板.     首先看一下原始的Ajax的调用的:      定义CommentController,代码如下: public class CommentController : Controller{private IList<string> _commen

一起谈.NET技术,在Entity Framework 4中映射现有实体类(POCO)

Entity Framework的全称是ADO.NET Entity Framework,是微软开发的基于ADO.NET的ORM(Object/Relational Mapping)框架. Entity Framework的主要特点: 1. 支持多种数据库(Microsoft SQL Server, Oracle, and DB2): 2. 强劲的映射引擎,能很好地支持存储过程: 3. 提供Visual Studio集成工具,进行可视化操作: 4. 能够与ASP.NET, WPF, WCF, W

一起谈.NET技术,在ASP.NET MVC3中使用EFCodeFirst 1.0

1. 新建项目 打开VS2010,选择 文件>新建>项目,新建ASP.NET MVC3 Web 应用程序,我这里把它命名为Blog. 2. 编写实体类 对于一个博客,一下几个类应该是必须的吧: Post                             博客文章类 Comment                     文章评论类,和Post是一对多的关系 Category                     目录类,和Post是一对多的关系 Tag                 

一起谈.NET技术,舍WebService 用.NET4中jQuery调用WCF

在我们之前的开发中,对于ajax程序,都是通过jQuery调用标记为[System.Web.Script.Services.ScriptService]的WebService,然后在WebService中调用后台的WCF.这成为WebService存在的唯一理由:作为ajax调用的中转站.如果要抛弃WebService,只需用WCF来作为中转站. 在.NET 3.5时代,jQuery就可以调用WCF,之前我也写过博客(比如:jQuery调用WCF需要注意的一些问题),但配置比较麻烦. 走进.NE

一起谈.NET技术,分享在MVC3.0中使用jQuery DataTable 插件

前不久在网络上看见一个很不错的jQuery的DataTable表格插件.后来发现在MVC中使用该插件的文章并不多.本文将介绍在MVC3.0如何使用该插件.在介绍该插件之前先简单介绍一下,推荐该插件的原因.在项目中我使用jqgrid比较多.但是发现当进行样式调整时jqgrid的样式常常会让美工头疼.而datatable插件却是一个轻量级的jQuery插件.当我通过浏览器查看该js插件rander后的源码.发现只是一个简单的html table,非常简洁.那么在没有特殊要求的情况下使用这个插件,开发