一
受到讲述最新版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
参考原文:
Workflow Foundation Internals (I):http://blogs.msdn.com/flow/archive/2010/02/21/workflow-foundation-internals-i.aspx
Workflow Foundation Internals (II): http://blogs.msdn.com/flow/archive/2010/02/21/workflow-foundation-internals-ii.aspx
Workflow Foundation Internals (III): http://blogs.msdn.com/flow/archive/2010/02/21/workflow-foundation-internals-iii.aspx