探秘WF4 Beta2中工作流对象模型

开发 后端
今天我们要讨论的是WF4 Beta2中的工作流对象模型,希望本文能对大家了解WF4 Beta2的一些特性有所帮助。

本文将讨论WF4 Beta2中的工作流对象模型,在.NET 4.0中工作流是变化比较大的一部分,希望那个通过本文能让大家对WF4 Beta2有一个全新的认识。

随着Visual Studio2010 BETA2的发布,大家对.NET 4.0技术的研究热情随之高涨。在整个.NET 4.0所引入的新技术中,工作流可谓是变化最大的部分。WF4与WF3几乎可以看成是两个完全不同的产品。

对于WF3的编程模型,已有相关的技术书籍介绍了,在网上也可以搜到有关的资源。但对于WF4,却几乎找不到任何深入介绍其对象模型的文章。

我以Reflector作为工具,反汇编了WF4的源代码,通过仔细阅读,粗步理出了一个头绪,在本文中进行介绍,期望能起到一个抛砖引玉的作用,帮助大家深入地把握WF4的技术内幕。

呵呵,第一次在博客园发文,希望大家多多鼓励。

1 Acitvity的继承树

在WF4中,Activity类是最顶层的基类。任何一个工作流都由至少一个Activtiy构成。以下是WF4中Activity的继承树:

WF4中Activity的继承树

 

在真实的工作流中,各个Activity可以相互嵌套,形成一个树型结构,最底层的叶子通常就是上图中最底层类(如CodeActivity)的实例。

最顶层的Activity类提供了一个可以供子类重写的InternalExecute()方法:

  1. internal virtual void InternalExecute(ActivityInstance instance,   
  2. ActivityExecutor executor, BookmarkManager bookmarkManager); 

子类可以重写此方法,在此方法中实现各种功能,这个方法在WF4内部非常重要,许多东西都与它相关。

为了方便地供开发者自定义业务处理逻辑,诸如CodeActivity之类最底层的类,另定义了一个抽象的Execute()方法:

  1. protected abstract void Execute(CodeActivityContext context); 

当开发者自定义Activity时,就可以直接地重写此方法。

简言之,工作流的运行就体现为Activity对象树中叶子节点Execute方法(或类似的方法,比如DynamicActivity是InternalExecute方法,AsyncCodeActivity是BeginExecute和EndExecute方法)的执行。

2.WF4中工作流的执行原理

首先要明确,在WF4中,如果使用WorkflowInvoker类来启动工作流时:

  1. WorkflowInvoker.Invoke(new Workflow1()); 

工作流Workflow1将在调用者的线程中执行。这种情况下,工作流的执行类似于方法调用,是最简单的执行模式。

然而,如果使用WorkflowApplication启动工作流,工作流实例将在调用者线程之外的另一个线程中运行:

  1. WorkflowApplication wpp = new WorkflowApplication(new Workflow1());  
  2. wpp.Run(); 

而且,这个“另外的工作线程”是线程池中的线程。

不管是由哪个线程负责执行工作流,有一个原则是很重要的:

单个工作流实例是单线程执行的,哪怕诸如Parallel Activity给你一个多分支“并行”运行的假象。

事实上,Parallel Activity采用在单线程中“轮换执行”各分支。当一个分支进入空闲“Idle”时,工作流调度器调度下一分支投入运行。所果所有分支都不包括使本分支进入Idle状态的Activtity(比如有一个Delay Activity或创建了书签),则Parallel Activity按从左到右的顺序执行各分支。

那么,构成工作流的各个Activity实例是如何执行的?

WF运行时在内部为每个工作流维护了一个工作项队列。然后,创建一个Scheduler类的实例来负责从此工作项队列中取出和追加工作项,并执行之。

这里要说说这个工作项队列,在Scheduler类的代码中可以找到它的声明:

  1. private Quack<WorkItem> workItemQueue; 

这里有一个奇怪的Quack<T>泛型类,我仔细看了一下,其实它就是一个泛型队列,但它有一点特殊之处:

Quack<T>泛型类在内部使用一个数组来保存数据:

  1. private T[] items; 

初始时,为队列分配可容纳4个T类型对象的内存空间,当不断增加对象而需要扩充空间时,就分配一个“当前所占内存空间*2”的新数组,再将老数组中的内容复制到新数组中。

很明显,在两个数组中复制元素会花费系统资源,我不知道为何WF4的设计者这样设计,估计是他们有其他的考虑。

队列中的WorkItem对象很有趣,它代表一个将被执行的Activity实例,这里暂时放下,一会儿还会介绍它。

Scheduler对象的工作可以简述如下:

它从队列中取出一个WorkItem对象,然后将其委托给线程池中的线程(如果工作流由WorkApplication以异步方式启动执行)或调用者线程(如果工作流由WorkflowInvoker以同步方式启动执行)执行。这些线程将负责调用WorkItem所封装的Activity实例的Execute()方法(或类似的方法,如前所述)。

3 深入分析Activity执行的流程
 一个Activity实例到底是如何执行的?一切得从WorkItem类开始。

WorkItem是一个抽象基类,提供了几个抽象方法,其中最重要的就是Execute()方法:

  1. internal abstract class WorkItem  
  2. {  
  3. //……  
  4. private ActivityInstance activityInstance;  
  5. public abstract bool Execute(ActivityExecutor executor,   
  6. BookmarkManager bookmarkManager);  

上述声明中还有两个很重要的类ActivityInstance和ActivityExecutor。

ActivityInstance代表着正在运行的一个Activity实例,它包容一堆的internal方法可以完成Activity的执行(Execute)取消(Cancel)和放弃(Abort)的功能。 ActivityExecutor则负责调用ActivityInstance中的这些方法。

WorkItem有一堆的子类,这些子类又派生出“孙”类。比如,其中的一个分支如下:

分支如下

 

不管有几个子孙,后代一般都重写了WorkItem所定义的Execute()抽象方法。

我们以ExecuteRootWorkItem类为例,顾名思义,这应该是与工作流中最顶层的Activity相对应的WorkItem。它的Execute()方法如下所示:

  1. public override bool Execute(ActivityExecutor executor, BookmarkManager bookmarkManager)  
  2. {  
  3. return base.ExecuteBody(executor, bookmarkManager, this.resultLocation);  

它将调用基类ExecuteActivityWorkItem的ExecuteBody()方法,此方法的关键代码如下:

  1. protected bool ExecuteBody(ActivityExecutor executor, BookmarkManager bookmarkManager, Location resultLocation)  
  2. {  
  3. //……  
  4. base.ActivityInstance.Execute(executor, bookmarkManager);  
  5. //……  
  6. }  

可以看到,它直接跳去执行最顶层基类WorkItem所定义的ActivityInstance对象的Execute()方法。此方法的代码如下:

  1. internal void Execute(ActivityExecutor executor, BookmarkManager bookmarkManager)  
  2. {  
  3. //……  
  4. this.Activity.InternalExecute(this, executor, bookmarkManager);  

注意ActivityInstance实际上封装了一个Activity对象:

  1. public sealed class ActivityInstance : ActivityInstanceMap.IActivityReference  
  2. {  
  3. public Activity Activity { getinternal set; }  
  4. //……  

所以,ActivityInstance对象的Execute()方法实际上执行的是Activity对象的InternalExecute()方法。再追踪下去:

  1. internal virtual void InternalExecute(ActivityInstance instance, ActivityExecutor executor, BookmarkManager bookmarkManager)  
  2. {  
  3. //……  
  4. executor.ScheduleActivity(this.runtimeImplementation, instance, nullnullnull);  

注意:上述代码是Acitivity对InternalExecute()默认的实现方式,它的子类(比如CodeActivity)通常会重写它。

可以看到,在ActivityInstance对象的Execute()方法中,执行流程转给了从前面一路传送过来的ActivityExecutor对象,由此对象的ScheduleActivity方法负责将Activity插入到工作项队列中。

ActivityExecutor.ScheduleActivity方法又进行了一个“倒手”,调用自己的ScheduleBody()方法:

  1. private ActivityInstance ScheduleActivity(……)  
  2. {  
  3. //……  
  4. this.ScheduleBody(scheduledInstance, requiresSymbolResolution, argumentValueOverrides, resultLocation);  
  5. }  

在ScheduleBody()方法中,“佛祖”终于现出真身,我们看到了Scheduler的身影:

  1. internal void ScheduleBody(ActivityInstance activityInstance, bool requiresSymbolResolution, IDictionary<stringobject> argumentValueOverrides, Location resultLocation)  
  2. {  
  3. if (resultLocation == null)  
  4. {  
  5. //……  
  6. this.scheduler.PushWork(new ExecuteExpressionWorkItem(activityInstance, requiresSymbolResolution, argumentValueOverrides, resultLocation));  
  7. //……  

在上述代码中,Scheduler对象将activityInstance转换为了一个ExecuteExpressionWorkItem,然后将其插入到工作项队列中等待执行。

现在我们看到,默认情况下,对ExecuteRootWorkItem的执行将导致一个新的ExecuteExpressionWorkItem工作项被插入到工作项队列中。

4.工作项队列中的工作项是如何调度执行的?

Scheduler类负责工作项的调度执行。

在Scheduler类的构造函数中,挂接了一个回调函数OnScheduledWork:

  1. static Scheduler()  
  2. {  
  3. //……  
  4. onScheduledWorkCallback = Fx.ThunkCallback(new SendOrPostCallback(Scheduler.OnScheduledWork));  

在OnScheduledWork()函数中,揭露出了任务项调度是如何进行的秘密:

  1. private static void OnScheduledWork(object state)  
  2. {  
  3. //取出队列中的第一个工作项  
  4. WorkItem firstWorkItem = scheduler.firstWorkItem;  
  5. if ((scheduler.workItemQueue != null) && (scheduler.workItemQueue.Count > 0))  
  6. {  
  7. scheduler.firstWorkItem = scheduler.workItemQueue.Dequeue();  
  8. }  
  9. else 
  10. {  
  11. scheduler.firstWorkItem = null;  
  12. }  
  13. //执行这一工作项  
  14. continueAction = scheduler.callbacks.ExecuteWorkItem(firstWorkItem);  
  15. //……  

下面是ExecuteWorkItem()方法的代码,可以看到,最后调度器还是委托activityExecutor来执行Activity的:

  1. public Scheduler.RequestedAction ExecuteWorkItem(WorkItem workItem)  
  2. {  
  3. Scheduler.RequestedAction objA = this.activityExecutor.OnExecuteWorkItem(workItem);  
  4. //……  
  5. }  
  6. ActivityExecutor的OnExecuteWorkItem()方法有很多代码,其中关键的就是以下这几句:  
  7. internal Scheduler.RequestedAction OnExecuteWorkItem(WorkItem workItem)  
  8. {  
  9. //……  
  10. propertyManagerOwner.PropertyManager.SetupWorkflowThread();  
  11. if ((abortException == null) && !workItem.Execute(thisthis.bookmarkManager))  
  12. {  
  13. return Scheduler.YieldSilently;  
  14. }  
  15. }  
  16. //……  

我们终于发现了调用工作项的Execute()方法的语句。

有的朋友可能会疑惑,我们的探索之旅从WorkItem.Execute()方法开始,转了一圈怎么又回到了WorkItem.Execute()方法?这样一来,调用工作项的WorkItem.Execute()方法将导致一个工作项被加入到队列中,然后当此工作项被执行时,它又将一个工作项加入到队列中,这会不会引发无限递归?

事实上这正是我们想要的效果。因为一个工作流实例实际上就是一个层层嵌套的递归的结构,这种设计使得执行其顶层Activity对象的Execute()方法时,会将其子Activity所对应的WorkItem加入到队列中加以递归执行。

很明显,对于那些不包容子Activity的Activity,我们应该“打断”这种递归执行的过程。WF4是怎么做到的?

以一个实例来说明更好。 请看以下自定义的Activity:

  1. public sealed class Prompt : CodeActivity  
  2. {  
  3. public InArgument<string> Text { getset; }  
  4. protected override void Execute(CodeActivityContext context)  
  5. {  
  6. Console.Write(Text.Get(context));  
  7. }  

注意上述Activity重写了基类CodeActivity的Execute()方法,此方法一执行完毕就会返回。

前面说过,对工作项队列中WorkItem.Execute()方法的调用,最终将转换为对ActivityInstance对象的Execute()方法的调用。而ActivityInstance又包容了最终的Activity对象实例,并将调用转给这一最终对象的InternalExecute()方法,为方便起见,重贴此方法代码如下:

  1. internal void Execute(ActivityExecutor executor, BookmarkManager bookmarkManager)  
  2. {  
  3. //……  
  4. this.Activity.InternalExecute(this, executor, bookmarkManager);  
  5. }  

在我们的自定义Activity中,没有重写CodeActivity的InternalExecute()方法(事实上也不可能,因为此方法是Sealed的),所以,被调用的实际上是基类CodeActivity的InternalExecute()方法。以下是CodeActivity的InternalExecute()方法代码:

  1. internal sealed override void InternalExecute(ActivityInstance instance, ActivityExecutor executor, BookmarkManager bookmarkManager)  
  2. {  
  3. //……  
  4. this.Execute(context);  
  5. //……  

非常清楚,它应用了多态特性,调用子类重写的Execute()方法,注意,它并没有调用最顶层Activity类所提供的InternalExecute()方法。

所以,问题的关键在于最顶层基类Activity的InternalExecute()方法,默认情况下,此方法将会通过 ActivityExecutor.ScheduleActivity()方法的调用将一个工作项加入到队列中,但CodeActivity没调用Activity基类的InternalExecute()方法而是重写了此方法,所以就打断了“递归”调用链。

5.小结

不知道朋友们是不是有点昏了,没办法,WF4内部就是有这么多的弯弯绕。   

简单地说:工作流执行时,所有需要被执行的Activity会被封装为一个WorkItem,被放到一个工作项队列中,然后由WF4调度器(其实就是Scheduler类的实例)负责从此队列中取出工作项执行。

工作项的执行可以由线程池中的线程承担。,也可以由调用者线程来承担。

WF4内部的工作原理非常复杂,事实上我们也没有必要了解其每个技术细节。但如果能了解其大致的内部原理还是非常有用的,它能帮助我们避开陷阱,真正地把技术用好。

对于技术,不仅要知其然,还要知其所以然。才能真正拥有了自由。

原文标题:探索WF4 Beta2的工作流对象模型

链接:http://www.cnblogs.com/bitfan/archive/2009/10/29/1592591.html

【编辑推荐】

  1. 浅谈WF 4.0 Beta1中的 跟踪机制
  2. WF4.0 Beta1中的规则引擎变化
  3. 浅谈WF 4.0 beta1的跟踪配置
  4. 详解工作流架构与实现
  5. 详解WF4.0 Beta2中的Switch<T>活动
责任编辑:彭凡 来源: 博客园
相关推荐

2009-10-22 08:54:56

WF4 Beta 2

2009-12-01 10:08:23

WF4属性

2009-10-28 09:23:27

WF4.0 Beta2

2010-01-14 09:35:10

WF4.0

2009-06-15 10:20:47

WF 4.0 Beta跟踪机制

2013-10-10 15:08:36

UbuntuUbuntu 13.1gnome

2009-11-25 11:31:36

Visual Stud

2012-06-12 13:23:58

LinuxLinux Deepi

2021-08-13 10:09:36

鸿蒙HarmonyOS应用

2022-10-26 08:00:43

Activiti工作流BPM

2021-10-14 11:34:05

技术工作流引擎

2013-06-25 10:13:11

iOS7WWDC苹果

2009-11-18 09:14:49

Visual Stud

2013-04-23 10:28:08

IBeamMDAAWF

2009-06-22 09:36:06

WF 4.0 beta跟踪配置

2009-12-14 09:40:50

VS 2008 Bet

2011-09-27 10:40:48

Ubuntu 11.1

2011-11-25 13:01:16

JavaMVCstruts2

2009-10-26 09:16:08

BigInteger类

2011-12-13 20:47:33

iOS 5.1
点赞
收藏

51CTO技术栈公众号