工作流内部工作原理

工作流内部工作原理

读过了Essential Windows Workflow Foundation这本书后,我觉得有必要为WF4也写一课。其实,WF4的工作原理和它的前一个版本几乎一模一样,但是它们的编程模型却是很不一样。我们会从可序列化的委托(Serializable Delegate)开始,慢慢深入去看WF4的设计。

首先我们先看一下CLR里对Continuation的支持吧。Continuation是一个程序执行中一个可以保存的时间点,所以它必须保存一个对执行代码的指针,而delegate就是一种这样的指针。Delegate最棒的地方就是它可以被序列化,反序列化,然后继续执行。我们可以看一下以下的代码:

代碼示例: SerializableDelegate

可序列化delegate给了我们一个方法去暂停一个线程,然后让它在另外一个进程中继续,这个进程甚至可以是在另外一台电脑上。这样做的好处很多,而最重要的就是我们除掉了所有的「亲和度」,代码再不依赖于进程,甚至不依赖于电脑。这样的话我们就可以很好的扩展我们的系统,只需要多买电脑,也任务分派到不同的机器就可以,关键是代码可以随时停下来。同时,我们有了控制。我们可以把序列化了的delegate删掉,这样就可以取消了这个进程。工作可以暂停很长的时间,而不用担心系统会佔用了内存资源。接下来我们看一下以下的代码示例,看看我们如何使用可序列化delegate把一个程序拆成不同的进程:

代碼示例: Version 1

执行这程序三遍,你会发现在控制台上会显示出“Hello world to homemade workflow foundation!”。在以上的代码,我们就简单的创建了一个简单的工作流程序了。不用说,这个范例有很多问题,我们有很多可以改善它的地方。第一步我们就先把对可扩展性的支持和业务逻辑分离出来。我们会提炼一个名为ReadReadWrite的类,然后把业务逻辑集中在它上面,这样我们就写了Version 2

代碼示例: Version 2

首先,留意我在主程序中写了一个while循环,这个只是一个方便的捷径而已。对于我们来说,知道这些程序能够分开就好,实在没有必要真的分开执行。这次的重构其实很简单直接,不过有一点值的留意的就是我把NextDelegate变成一个ReadReadWrite的一个成员。这样做的原因是为了让每一个delegate都有一样的signature.

到了这个时候,主程序和业务逻辑已经是分离了。我们还是有一些耦合点,例如主程序需要知道程序的起点是RunStep1,而储存接续点的成员是NextDelegate,这样我们的主程序就不能是一个普遍而适用于任何程序的主程序。去掉这些耦合的方法说也简单,就是定义一个父类,让主程序与父类耦合而不会和业务逻辑耦合,这个父类我们命名成Activity.

代碼示例: Version 3

这次的重构也是非常的简单。读一下ReadReadWrite的代码,这样的代码已经不难写。其中有一点我们不喜欢的地方就是ExecuteRunStep2里面做的事情几乎一模一样,从代码的角度去看最好我们可以重用。我们试着提炼,然后发现两个问题。一个是它们是在更新不同的状态变量,而另外一个是它们返回不同的delegate。它们返回不同delegate的这问题尤其严重,因为读文件和程序执行顺序是两件垂直的事情。我们把程序控制和实际工作混合在一起会破坏了代码的内聚性。接下来我们试着把它们区分出来。

代碼示例: Version 4

Sequence activity 的实现一点也不简单,而且它也不是最优化的。下一篇我们会讨如何优化Sequence。现在我们先专注于除掉Read1Read2之间的重覆。到这里Read1Read2都是在读文件了,所以我们可以通过名字去找状态变量,这样就可以完全的把重覆除掉了。

代碼示例: Version 5

走到这里,我们已经可以写能重用的Activity,而且这些Activity都不用管有关序列化的细节。不去读Main的代码,我们甚至不知道序列化有实际进行。看看ReadWrite,是不是开始有点像WF4ActivityAPI呢?下一篇,我们会继续这个例子,去探讨为甚麽Activity.Execute会有一个参数,和这个参数如何把程序兴资料区分出来。

接续上一篇,我们会去分离程序和资料。对程序员来说,程序和资料是非常不一样的概念。比如说,你可以执行程序,却不可以执行资料。程序代码在执行过程中不会被修改(除了少数比如hook或者overlay的例子除外),但是资料却没有这样的限制,甚至我们觉得资料常常被修改是再正常不过。对于处理器来说,它们并没有多大的区别,都是一堆bytes。从一个操作系统的角度去看,代码不会修改这一点可以用来优化内存的使用,因为代码只要存一份就够了,可以用来起几个进程。

同样的道理也可以使用在Workflow上。我们做这样的比喻,Sequence就是程序,但是sequenceCounter只不过是执行资料而已。sequenceCounter这个成员定义在Sequence上使它们不可区分。想像同样的一个Sequence有很多个进程在跑,那么sequenceCounter就会乱掉。

读一下Activity这个类的代码,States定义在Activity上其实是个更大的问题。与其让States通过activity去读取,我们会把它变成一个Execute的参数。同样地,NextDelegate也不应该放在Activity上。不过这个问题更麻烦一点,因为Sequence需要同时有两个NextDelegate。去解决这个问题,我们会使用一个Stack<Frame>去存这些NextDelegate物件。勤快的读者们,请你们亲手试一试,因为我们在下一个版本会有很多的改动如下 

代碼示例: Version 6

我尝试把Version 6Version 5的改动减到最少了,在把delegate的存取方式改动的同时,我们也优化了Sequence的执行。现在执行Sequence下的Activity再也不会通过Sequence。现在的代码能跑了,我们就该重构。这次我希望States把它的Stack<Frame>给封装起来。这样这个Stack的逻辑就不会四处分散。说也不是很难,只要把Frame变成private就可以驱动这些重构了,我做的不过就是给操作一个合适的名字而已。

Version 7 没有甚麽实质改动,只是封装一下而已。 
代碼示例: Version 7

现在我们已经很接近了,在这个版本里我们只对States做序列化。我们问一下自己,到底我们做完程序和资料的分离没有?答桉是没有,因为States里有Delegate,而delegate又回到Activity物件,即程序。我们可以通过序列化MethodInfo,而非delegate,去达到这一点。我们在序列化MethodInfo的同时,也需要存ActivityIDActivityID是一个Activity的唯一身份码,我们可以通过深度搜寻去找出来。由于这些代码比较複杂,也没有甚麽太大的技术难点,在这里我们就轻轻带过了。另外一个问题就是我们没有保证程序是唯读的。这问题有多种解决的办法。在WF3中它採取了先拷贝一份,永远拷贝来执行的方法,而WF4则选择做一个拷贝(只拷贝对执行有作用的资料),跑原来的一份,而在跑的时候保证跑的确实是原来的拷贝。我们也可以选择当用户更新程序时让它出错。同样地,这些都比较明显,就略过不写了。

还有另外一个问题,就是一行的程序(比喻为一个Activity)可以被多次执行。最简单的例子就是迴圈。这时候,它们的储存必须定义在Frame上,实际上,所有的资料都应该存在Frame上,这就没有States的全局性了,这是WF4比它前一个版本优胜的地方。

看着States的实现,我们不难发现ScheduleActivity只能被调用一次,因为Frame上只有一个NextDelegate成员。下一篇我们会去改进ScheduleActivity, 允许多个未完成的Activity,我们也会讲一讲如何让Workflow和主程序如何使用Bookmark通讯。

接续上一篇,我们会允许有多个Activity同时被Schedule,换句话说,就是某程度上的并行处理。这情况其实很普遍,比如说多人需要审批一份文档。上一次我们发现ScheduleActivity只能被调用一次,那麽明显的解决方桉就是允许一个States上可以有很多的Delegates。看着states.Frames,我们过往的实现都只会盯着最上层的一个Frames,但是在并行的情况下这样就不成了。我们需要一个像Stack一样,但是有很多个顶的一个数据结构,想一想,这不就是一颗反过来的树吗?我们会在数据结构上放这个棵树所有的叶,然后用父指针去找它的Parent Frame。当我们有很多的叶的时候,只有States就不够了。Activity.Execute将无法知道它应该是在那一个Frame上跑,所以我们要把Execute的参数从States改成Frame,这样的话其它就会变得简单了。

代碼示例:Version 8

我们还是在使用以前的Activity,这只不过是为了安全起见,我们没有使用并行。现在我们使用并行了,最简单的就是Parallel,它只会Schedule两个Read 

代碼示例:Version 9

留意工作还是单线程的在执行。Parallel只不过允许多份工作同时在等着而已。当这些工作实际上是在等外界输入,那它们就是在同时等待。在上一个版本,我们每一个Delegate的先决条件都是Frame,但是很多时候有些工作不能完成不是因为其它工作还没完成,而只不过是它所需要的资料还没到而已。这些资料到最终还是从主程序中获得。我们可以通过States去通知主程序去接收资料,到资料收到的时候,主程序便能执行正在等待数据的工作了。WF3使用Queue的方式去等待数据,但我们发现很多时候我们只需要一个数据项,所以WF4使用了Bookmark 

代碼示例:Version 10

到这里我们就结束我们这旅程了。当然,要做到像今天的WF4还有很漫长的路,但是我希望通过这几篇,大家可以瞭解WF的使计,并可以好好的把它运用到实际场景里,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值