一步一步教你实现一个工作流运行时

    受到讲述最新版Workflow Foundation的<<WF本质论>>(WF3.0 3.5)这本书的启发,我不由自主想写写WF4.0。虽然说基本工作原理根本上相同的,但是编程的模型却相差甚远(WF3.0与WF4.0之间)。本篇文章中,我们也将看到WF4是如何作出了设计决策。

    首先,让我们复习一下底层的CLR技术Continuation。Continuation能让保存恢复执行,因此,它需要包含可执行代码的指针。委托用来实现这个目的。委托有一个很大的优点,它能够双向地存储和恢复二进制文件,存储的文件仍然可以继续执行。下面代码段显示委托的双程运作。(代码见附件)

    Code Sample 1: SerializableDelegate

    序列化委托提供了一个机制,能让我们暂停运行托管的线程,在另一个进程中恢复(也许在另一台计算机)。这样做有很多好处。其中最重要的是,我们移除了'亲和力'。代码不再附在原来的进程,甚至原来的机器。这样允许我们通过简单的添加的计算机来扩展应用程序。此外,我们现在有一些控制。例如,我们可以删除序列化的委托,而不是恢复。这样,我们是取消了执行。同样,我们可以暂停几天执行,而且不必担心内存的使用。让我们通过这个代码示例,看看如何通过序列化的委托把一个程序分成几个进程完成。

    Code Sample: Version 1

    运行这个程序三次,你将会在控制台上看见“Hello world to homemade workflow foundation!” 输出。上面的代码已经为我们设计出第一个最原始的工作流应用程序。不用说,这是非常的粗糙,有很多需要改进。首先,我第一步进行可扩张性地分离(事实上,现在我们是在业务逻辑中序列化委托的)。为了实现这个,我添加一个名字为ReadReadWrite类。将业务逻辑移动到这个类里面。这是我们的第二版:

 

Code Sample: Version 2

    Version 2中,首先我在主程序里面放置了一个while循环这样就不用去运行程序三次。这样就比较方便。这是非常容易让我们了解到将它们分解成不同的程序,但我们没有这样做。重构是最简单的,但是在这里有一点是值得注意的,在ReadReadWrite中使用了NextDelegate属性,这是为了让所有的委托能分享一个统一的签名。
    现在宿主程序和业务逻辑是分开的。在这一点上,我们仍然有两个宿主和业务逻辑之间的耦合。宿主需要知道是从RunStep1开始的。宿主还需要知NextDelegate是存储延续的属性。这些耦合使得宿主不通用。我们可以消除这些耦合通过为ReadReadWrite定义一个基类,我们称它为Activity。

Code Sample: Version 3

    重构很简单。看下ReadReadWrite,现在这些代码很容易写了。有一点我们都不喜欢的,Execute方法和RunStep2方法都是读取文件的代码,最好是将逻辑分享到可重用组件中。为了处理这个问题,实际上我们了解到存在两个障碍。一个是他们是更新不同states,以及返回不同的委托。它们返回不同的委托,事实上也是一个问题。因为这些委托是真正的控制逻辑,都是读取文件。我们把他们放在一起,这样逻辑就不连贯。现在,我们通过拆分结构,进一步优化他们。

Code Sample: Version 4

    写Sequence活动是不简单的事情,现在离理想情况还很远。我们将Sequence的优化推迟到下节讨论。现在我想侧重于移除Read1和Read2之间的重复。对于上面的代码,现在Read1和Read2其实只是读文件。最后一步,合并这两个类,使它们通过名称访问states,而不是一个静态字段。

Code Sample: Version 5

    现在我们已经访问这个用于编写可重用的活动states 。这些活动不必担心自己的序列化。如果没有阅读的Main的代码,甚至不知道序列化的发生。Read或Write,看是否像我们Activity的API?在本系列的后面,我们将继续这方面的例子,说明为什么Execute方法还有一个的参数,以及如何从数据中分离出程序。

 

 二

    继续我们的上一篇文章,我们工作是将程序和数据分开。从一个程序员的角度来看程序和数据是非常不同的概念。例如,您可以运行代码,但不能运行数据。在运行时代码将不会被改变(除非一些情况,如hook和overlay),但数据将会一直在改变。从处理器的角度来看,他们没有什么不同。他们只是一些字节。从操作系统的角度来看,读取代码事实上是优化主内存的使用。因为代码的单个副本可以运行于多个进程。

    完全相同的观点同样适用于工作流。我们认为,Sequence作为程序,而sequenceCounter只是执行数据。sequenceCounter被定义在Sequence类中也使他们密不可分。考虑到多个工作流程会运行在相同的流程定义,sequenceCounter就会混淆,造成一些问题。

    阅读一下Activity的代码, States被定义在工作流中其实是个很大的问题。我们无需将它定义成活动的保护属性字段,而是可以从Execute方法参数中检索。同样,我们也不应将NextDelegate定义在活动中。这样会越来越复杂,因为Sequence执行需要存储两个委托对象。为了解决这个问题,我们将使用Stack<Frame>持有这些对象。建议勤奋的读者自己试验一下,因为我们从这个版本到下一个版本是一个很大的飞跃(而不是小小的重构)如下所示。

Code Sample: Version 6

    我试图保持从版本5到版本6尽可能小的修改。由于我们将委托储存从Activity移动到states中,我们还优化Sequence的执行。现在,执行特定的活动将不会通过sequence,而只是将sequence的延续保存在堆栈中。特别地,我们将会把更多的逻辑放在states中,使堆栈是由states维护,而不是由Sequence和Program分配。使Frames为一个私有字段,驱动所有一切。

版本7没有结构性变化,只是堆栈封装。

Code Sample: Version 7

    我们已经非常接近完成了。现在对于states对象序列化,我们应该完成的分离吗?答案是No。这是因为states包含对委托的引用,然后委托将链接回该程序。为了打破这种联系,我们不能再序列化委托了。我们将序列化的MethodInfo和活动,以及活动的ID,活动的ID可以通过活动树的遍历取得。我们将跳过这,因为这个使示例复杂了。另一个问题是程序没有被限定为只读。有多种解决问题的办法。WF3选择使用了程序的一个副本,并始终运行这个副本,同样WF4选择使用程序的一个副本和在运行时验证主程序是不能修改的。当程序在运行时改变,我们可以使报错给用户。这样又使示例复杂了,在这里我们将停止这方面的讨论。尝试执行或阅读怎么去实现的是一个很好的学习方法。

    另一个程序和数据大问题,是一行程序在一个进程中可以在执行多次。最简单的例子是一个循环。这些数据被存储在frame中而不是states。事实上,所有states应被存储在一个frame中。它又储存states本身,使它为全局变量。有了frame,我们可以控制的变量范围。这是一个相对上一个版本WF的很大的进步。

    看看States实现情况。其中一个问题是ScheduleActivity只能调用一次,在接下来的文章,我会谈谈提高ScheduleActivity,允许它有多个未完成的活动和工作流程,以及通过书签进行宿主通讯。

     继续我们的上一篇文章中,我们工作是允许并行和创建工作流程/宿主通信模式。这种模式实际上是非常普遍,如多个审批人等待批准文件。上面我们看到的问题是,ScheduleActivity只能调用一次,因此,最明显的解决办法是允许states放置多个委托。Frames,这是一个堆栈,我们随时访问堆栈的顶部。为了具有并行,我们希望有一个这样的数据结构的堆栈,它有多个顶部。想想看,其实这仅仅是一颗树。在我们的数据结构中,我们将保存所有的叶子,以及指向父frame的指针。通过多顶部,执行指向所有states指针的活动将是不能工作的,因为活动不知道它在的frame。我们将委托将从Action<States>改成Action<Frame> ,其余的将会变得清楚。

Code Sample: Version 8

   因为我们想保证安全,我们使用的是旧的活动,在上个示例中没有并行的功能,现在我们将介绍并行,Parallel是最简单的开箱的Activity ,这里我将使用Parallel调度两个Read。

 Code Sample: Version 9

   注意尽管使用了Parallel活动,工作还是顺序执行。基本上并行活动允许有多个未完成的工作。当这些工作项目正在等待外部信号,这些外部信号都等待着所有的都结合在一起。我们用frame作为委托依赖,但有时委托没有准备好执行不是因为控制依赖,而是数据依赖。这些数据最终来自宿主。来自states,我们可以发信号给宿主等待这些数据,来自宿主,数据已准备就绪,这些依赖可能被打破。WF3通过WorkflowQueue提供了这样的机制,WF4通过书签实现这一点。程序实际上只是要求一个项目时,Queue有时显得很过分。

Code Sample: Version 10

    这将是我们的home made WF系列的结束,当然我们还有很长的路要走。但我猜直至现在你应该可以欣赏WF带给您什么,以及如何最好地为您的应用程序服务。

示例代码:/Files/zhuqil/HomeMadeWF.zip






本文转自麒麟博客园博客,原文链接:http://www.cnblogs.com/zhuqil/archive/2010/03/01/home-made-workflow-runtime.html,如需转载请自行联系原作者

前 言 1 1 概 述 2 1.1 选题背景 2 1.2 组织结构 2 2 所用相关技术和方法 3 2.1 工作流 3 2.1.1 什么叫工作流 3 2.1.2 工作流发展 3 2.1.3 工作流的优点 3 2.2 MVC工作模式 4 2.2.1 MVC设计思想 4 2.2.2 MVC的具体实现 5 2.2.3 MVC的不足 6 2.3 JSP技术介绍 6 2.3.1 JSP的运行原理 7 2.3.2 JSP的生命周期 8 2.3.3 Servlet和JavaBean技术介绍 8 2.3.4 Java 虚拟机 9 2.3.5 JSP访问SQL Server 2000数据库 9 2.4 数据库后台环境配置 10 2.5 系统开发工具简介 10 2.5.1 Dreamweaver 10 2.5.2 MyEclipse 10 2.5.3 Tomcat 11 2.5.4 SQL Server2000 11 2.5.5 chs_sql2ksp3 12 3 系统需求分析 13 3.1 系统功能分析 13 3.2 系统性能分析 13 3.3 系统方案的确定和评价 13 4 系统总体设计 15 4.1 系统层次模块图 15 4.1.1 营业厅模块 15 4.1.2 收费管理模块 16 4.2 系统数据流程图 16 4.3 数据表设计 18 5 详细设计及编码 21 5.1 编写JAVABEAN 21 5.2 营业厅实现函数 21 5.3 收费厅主要的实现函数 22 5.4 JAVABEAN主要实现模块 22 5.4.1 中文字符格式的转换模块(Stringto.java) 22 5.4.2 自动生成验证码(Ran.java) 22 5.4.3 数据库的连接(ConnectionFactory.java) 23 5.4.4 数据库连接的关闭(DatabaseUtils.java)--只提供接口 23 5.4.5 密码修改模块(Common_fuction.java) 24 5.4.6 间格式转换(timeBean.java) 24 5.4.7 数据统计(counthander.java) 25 5.4.8 营业厅的接口(luruaction.java) 27 5.4.9 营业厅的主要函数实现(luruhander.java) 28 5.4.10 收费厅的主要函数接口 32 5.5 管理员登陆模块 33 5.5.1 管理员登录 33 5.6 营业厅管理模块 36 5.6.1 Left.jsp页面 36 5.6.2 Work.jsp 40 5.6.3 customerlistinfo.jsp 41 5.6.4 allinfo.jsp 41 5.7 收费厅管理模块 42 5.7.1 Left.jsp 42 5.7.2 Work.jsp 43 5.7.3 Customerlistinfo.jsp 43 5.7.4 gongdan.jsp 43 6 系统测试维护 45 6.1 测试目的 45 6.2 测试环境 45 6.3 系统测试 45 6.4 系统维护 45 7 开发难点技术 46 7.1 主要程序实现的代码描述 46 7.1.1 验证码的自动生成 46 7.1.2 生成WORD工单 46 7.1.3 以一定的间刷新页面 47 7.1.4 JSP中文问题的解决 47 7.2 在程序编码过程遇到的主要问题: 48 7.3 代码编写风格 49 7.4 我的不足: 49 结束语 50 致 谢 50
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值