WF(二)活动基础
活动是WF 中workflow 处理的基本单位,可以把他们分成两大类:组合活动(包含其他活动)、基本活动(单一任务的活动)。本篇内容包括:Activity、CompositeActivity;ActivityExecutionContext(AEC);SequentialWorkflow和StateMachineWorkflow;策略和规则。
1.Activity、CompositeActivity
所有的活动的基类都是Activity,它是工作流的基本构造块。因为 Activity 派生自 DependencyObject,所以属性可以定义为标准 CLR 属性和依赖属性。
活动在其生存期内可以有六种状态。 这些状态分别为 Initialized、Executing、Canceling、Closed、Compensating 和 Faulting。
在 Initialized 状态期间,将为活动创建 ActivityExecutionContext,并将执行特定于该活动的其他初始化详细信息。 例如,某些 Windows Workflow Foundation 活动(如 SuspendActivity)会在初始化期间检查其是否具有父级复合活动。
当某个活动进入 Executing 状态时,将会执行该活动的主要功能。
活动的 Canceling 状态可以由父活动显式置入,也可以因为在执行该活动期间引发异常而置入。
Closed 状态是活动的最后和最终状态。 需要注意的一个问题是,如果某活动成功完成,但根据业务逻辑随后必须经过 Compensating 活动。 因此,该活动将会从 Closed 转换到 Compensating,然后在完成补偿逻辑后转换回 Closed。
如果在活动的 Executing 状态、Canceling 状态或 Compensating 状态期间引发异常,活动将转换到 Faulting 状态。
下面的流程图演示了活动如何在各种活动状态之间转换。
对于组合(容器)型的活动,大多都继承了CompositeActivity类,该类派生自Activity类,使用在 Activity 类上定义的 ActivityExecutionContext 类方法和事件来管理其子活动的执行。例如,使用 ActivityExecutionContext 的 ExecuteActivity 来计划子活动的执行(由符合活动执行)。当子活动完成执行后,通过订阅在 Activity 中定义的 Closed 事件,可以向 CompositeActivity 活动发出通知。
2.ActivityExecutionContext(AEC)
ActivityExecutionContext (AEC) 是在宿主应用程序调用 Start 方法时为活动创建的执行环境。所以从工作流运行时的角度来看,OnActivityExecutionContextLoad/Unload定界了活动的生命周期,即:
1.OnActivityExecutionContextLoad
2.Initialize
3.Execute
4.Uninitialize
5.OnActivityExecutionContextUnload
6.Dispose
AEC 提供了一种复合活动,该复合活动具有执行 (ExecuteActivity) 或取消 (CancelActivity) 子活动的能力。 它也可以通过 CloseActivity 方法来关闭自己。 这些是仅有的父活动可以通过 AEC 控制的执行状态更改。 所有其他活动状态都是由工作流运行时引擎控制的。它代表了活动的执行环境,容纳一些和正在执行的活动相关的信息,工作流运行时,需要不时的访问这些信息。
AEC 具有名为 ExecutionContextManager 的属性,使其可以生成新 AEC。 这些 AEC 是父活动(如 WhileActivity 活动、ReplicatorActivity 活动或 ConditionedActivityGroup 活动)每次运行其子活动超过一次时生成的。 每次迭代都使用其自己的 AEC 创建一个克隆的活动,因此子活动的这些不同实例可以独立运行(而对于 ReplicatorActivity 活动则可能并行运行)。
下面通过一个例子来查看这一点:下载
首先在codeActivity4上设一个断点,执行,while活动一共会执行两次,第一次与第二次的调用栈截图如下:
我们可以看到,WhileActivity 创建了两个Parallel的副本。
活动服务的提供者
AEC可以作为服务的提供者;这些服务是访问WF程序实例外部功能的窗口。AEC实现了System.IServiceProvider接口。为了方便使用,还提供了一个泛型的GetService方法-------GetService<T>。活动可以使用这些方法来取得完成任务所需要的服务。
事实上,AEC把它的服务提供者链接到WF运行时的服务提供者上。这就意味着活动可以获得由WF宿主程序提供的自定义服务。同时也使得,服务可以不必了解服务的实现细节,而随时更换服务的实现,而不影响活动的代码。实例代码如下:
protected override sealed ActivityExecutionStatus Execute(ActivityExecutionContext context)
{
MyService myService = context.GetService<MyService>();
myService.XX();
}
3.SequentialWorkflow和StateMachineWorkflow
通过workflowInstance的GetWorkflowDefinition 方法可以获得实例的根活动,返回Activity类型。更进一步,我们的根活动都是组合活动,也就是都继承于CompositeActivtity。再对应到我们设计框架的工作流,有两种,分别是顺序工作流和状态机工作流。我们设计工作流时,实际上都是创建了继承于其中某个类的实例。
顺序工作流:WF任务可以自知的执行,很少由外部进行控制,主要由WF自身来执行的任务进行控制,只有少量用户或者没有用户来和它进行交互。(如果您还是缺乏认识,可以参考一下 飞林沙的工作流模拟登陆)
状态机工作流:WF严重依赖于外部来控制和指示来执行,预期有很多的用户交互(或外部控制)。
4.策略和规则
对于一些业务逻辑,比如:“该账户是否有月?是否允许透支?。。。。。”。对于这一类的情况,我们早已习惯使用过程化得处理方式来对关系进行处理:
If(....)
........
Else
.......
但是,对于多情况的检查,将会变成许多嵌套的if语句,switvh语句和循环,这明显是不利于维护的。WF中提供可一种更好的方式,我们可以创建声明性规则然后使用规则引擎(rules engine)来处理它们。
规则:通过条件来表示,返回一个Boolean的值,并伴随着一个或多个操作,在条件处理和策略的时候会用到规则。
策略:规则的集合。它被包含在一个规则集(RuleSet)中。
.rules文件
声明性的规则条件,将被序列化到.rules文件中,生成的.rules文件中规则以代码DOM语句的形式表示。WF使用.NET Freamwork中的System.CodeDom提供的类型。可以使用这些类型来创作条件。详细可以参看:坚持学习WF(20):规则引擎中的活动条件
RuleSet
我们将规则视为IF-THEN-ELSE语句,其中,条件对应于IF而操作定义THEN和ELSE字句的行为。这样的一组规则便成了规则集RuleSet,规则集既支持规则的简单依次序列,也支持规则的复杂正向链接。规则集可以由Policy活动执行。下图就是VS2008中的规则集编辑器:

|
优先级(Priority) |
以整数来设置Rule优先权,值越大优先级越高。优先级高的将先被执行。 |
|
激活(Active) |
是否启用该规则 |
|
重新计算(Reevaluation) |
是否对规则的判断条件进行重新应用 始终:如果规则条件被修改,则会被重新应用 从不:被修改以后,不会被重新应用 |
|
条件(Condition) |
条件判断表达式 |
|
Then 操作 |
条件判断为“真”时要执行的操作 |
|
Else 操作 |
条件判断为“假”时要执行的操作 |
以下摘自坚持学习WF(21):规则引擎中的RuleSet,以及《WF从入门到精通》
正向链接
早期作出的判定结果也会影响到后期将怎样去进行判定,这就是正向链接的本质。规则紧密地链接在一起,就像一个规则的判定结果会影响到接下来的规则会怎样去进行判定。
两个或更多的规则共享了相同的工作流字段或属性。假如没有规则和其它的规则共享访问相同的工作流字段或属性,则 这两个规则之间也就没有依赖关系。假如存在依赖关系,则这个问题将通知规则引擎存在着依赖关系,在有些情况下也有可能要掩盖这些依赖关系的存在。
通常在三种情况下规则可以是正向链接的:隐式链接(implicit chaining)、特性声明链接(attributed chaining)和显式链接(explicit chaining)。
隐式链接
当字段和属性被一条规则进行了更新,而这些字段或属性又显而易见地被其它规则所读出的时候,就会产生隐式链接。例如,考虑这些规则:
IF this.OrderQuantity > 500 THEN this.Discount = 0.1
And
IF this.Discount > 0 && this.Customer == "Contoso"
THEN this.ShippingCost = 0
第一条规则在订单数量超过500 单位时将进行打折。第二条规则陈述了假如公司是
Contoso 并且也进行了打折,则对运费免费。假如第一条规则起作用的话,第二条规则可能需要再次进行重新判定并执行。
特性声明链接
因为在你的工作流中的方法能对字段和属性进行修改,但是规则引擎可能对此却一无所知,因此WF 提供基于规则的特性,一共三个RuleReadAttribute,RuleWriteAttribute 和RuleInvokeAttribute 类分别表示用于读取条件、写入操作和调用方法的属性。结合先前的例子,对规则稍微进行改写,特性声明链接可能看起来就像下面这样:
IF this.OrderQuantity > 500 THEN this.SetDiscount(0.1)
AND
IF this.Discount > 0 && this.Customer == "Contoso"
THEN this.ShippingCost = 0
这里,第一条规则调用了工作流类中的一个方法:SetDiscunt,它对Discount 属性进行了更新。但规则引擎并不知道SetDiscount 将改变Discount 的值,因此当写SetDiscount方法时,你应当使用RuleWrite(或者RuleInvoke)特性:
[RuleWrite("Discount")]
private void SetDiscount(decimal discountValue)
{
}
RuleWrite 特性将通知规则引擎对SetDiscount 的调用将导致对Discount 属性进行更新。因为这些形成了一个依赖关系,因此假如SetDiscount 方法被调用的时候这些规则将会被重新进行判定。
显式链接
最后一种正向链接是显式的,也就是说你的规则中使用了Update 语句来告知规则引擎有一个字段或属性的值已经被修改了。Update 的作用等同于使用RuleWrite 特性。但是正如你所知道的,当调用一个工作流方法的时候,规则引擎并不知道该方法是否对某个规则依赖到的字段或属性进行了更新。在这种情况下,你调用了工作流方法后接着通过使用一个Update语句来告知该规则引擎存在的依赖关系。
这些或许听起来有些古怪,但它还是有使用价值的。假如你写你自己的工作流的话,你应该使用基于规则的特性。但是,当基于工作流的软件变得普遍, 人们开始使用第三方的工作流的时候,他们可能会发现基于规则的特性并没有应用到各个工作流方法中去。在这些情况中,他们就应当使用Update 语句来保持正确的工作流状态以及规则引擎的同步。基于规则的特性是以声明的方式来指明更新,而Update 语句则是在不可避免的时侯使用。当使用了已预编译过的第三方软件的时候,你就需要这种不可避免的方案。
回到先前的例子,假设SetDiscount 方法并没有应用RuleWrite 特性。则这两条规则就会和下面的这些看起来相像。
IF this.OrderQuantity > 500 THEN this.SetDiscount(0.1)
Update(this.Discount)
And
IF this.Discount > 0 && this.Customer == "Contoso"
THEN this.ShippingCost = 0有了这些信息,规则引擎就知道了Discount 属性已经本更新了,并且也因此将对规则
的适用范围进行重新判定。
控制前向链接
RuleSet类的ChainingBehavior属性表示获取或设置 RuleSet中 Rule 类的正向链接行为。决定规则引擎是如何执行正向链接的,这个属性的值是枚举类型RuleChainingBehavior。下面是各个取值的说明:
None(顺序的):指示不执行链接。规则只执行一次,规则从顶到底地在单一的通道上被判定。假如存在依赖关系,这些依赖会被完全忽略
UpdateOnly(仅显示更新):指示如果已执行的操作使用 RuleUpdateAction 明确指定链接,则执行该链接,会使隐式的和特性标记的前向链接无效,在省略了Update 语句的地方,规则引擎不会做任何确定是否存在依赖的尝试,因此即使实际上存在依赖关系,规则也将不会被重新进行判定。它的作用是在你的规则中增加了Update 语句后,你就在前向链接上掌握了完全的控制权。你可能会这样去做以提高性能(因为规则引擎就不再对所有那些非必要的规则进行重新判定),你也可能必须去这样做以便消除你的规则中的依赖循环。
Full(完全链接):能让规则引擎根据需要去对包括隐式的和特性的在内的规则进行重新判定
指示由操作修改字段或属性、为由操作调用的方法指定 RuleWriteAttribute或执行 RuleUpdateAction时执行该链接。
下面举个例子:
|
Rule |
Conditon |
ThenAction |
ElseAction |
|
Rule1 |
A>10 |
B=60 |
B=40 |
|
Rule2 |
B>50 |
C="preferred" |
C="normal" |
|
Rule3 |
D<100 |
B=B*0.80 |
|
我们可以看出A和D是输入变量,B和C是规则中设置的。如果我们使用完全正向链接的选项,规则引擎会自动根据这些规则来觉得是否需要重新计算之前的规则,比如A是12,D是99的话,
1. Rule1 A>10 所以B=60
2. 到了Rule2 B=60,大于50,所以C="preferred"
3. Rule3 D=99 <100 所以B=60*0.80=48
4. 这个时候B=48,B的值改变了,所以Rule2会重新计算,B<50 C="normal"
5. Rule3不会重新计算,因为他包含的变量自从第一次计算后没有变化。
最后的结果是B=48,C="normal",A D没有变化。如果你将正向链接的属性设为None属性的,每个规则将执行一次,最后的结果将是B=48,C="preferred"。
使用Update语句
有的时候我们不需要完全的正向链接,我们使用UpdateOnly,UpdateOnly 选项关闭隐式的、基于属性的链接,并规定链接只应对显式 Update 语句发生。 这使您能够完全控制哪些规则引起重新计算。 这个时候我们必须使用Update语句,通过这个语句可以告诉WF,某个属性一定会被修改了,相关的规则可能需要重新应用。
下面举例说明,
|
Rule |
Conditon |
ThenAction |
ElseAction |
|
Rule1 |
A>10 |
B=60 ,Update(B) |
B=40 ,Update(B) |
|
Rule2 |
B>50 |
C="preferred",Update(C) |
C="normal",Update(C) |
|
Rule3 |
D<100 |
B=B*0.80,Update(B) |
|
Update语句在序列化到.rules文件的时候会生成RuleUpdateAction,每个Update语句表示是否被其他条件更改,使用此选项可以避免导致规则过度(甚至是失控)重复执行的循环依赖性,或者通过消除为提供 RuleSet 的功能完整性所不需要的规则重新计算来提高性能。
禁止规则重算
另一个影响规则计算的是Rule类的ReevaluationBehavior属性,获取或设置一个值,该值指示是否可以重新计算 Rule。其值为RuleReevaluationBehavior,默认是Always,还有Never.对应于上面图中的重新计算选项。
Always :为默认值,它提供了前面讨论过的行为,即,总是根据其他规则的操作所引起的链接重新计算规则。
Never: 顾名思义就是关闭重新计算。 规则计算一次,但如果该规则先前已执行了任何操作,则不进行重新计算。 换言之,如果先前计算过该规则,并因此执行了其 Then 或 Else 操作,则不会重新计算该规则。 但是,执行 Then 或 Else 操作中的空操作集合并不表示规则已经执行。一个例子可能包括以下内容:
IF this.Handling < 5.0 && this.OrderQuantity > 500 THEN this.Handling = 0
这条规则的意思是:“假如手续费低于$5.0 并且订单数量超过了500 个单位的话,那
么就不收取任何的手续费。”但是当满足该规则判定标准并且把手术费设置为0 时会发生什
么呢?哦,依赖属性Handling 被更新了,因此该规则要重新判定!假如你猜到该规则会导致一个无限循环的话,你猜中了。因此,应用一个Never 类型的重新判定模式很有意义:手续费用一旦为0,为什么还需再次对规则进行判定呢?尽管在写这个特定的规则时可能使用其它的方式来防止出现无限循环,但问题是在你的工作流创作工具包你有这样一种重新判定模式来作为工具,你又为什么不利用它呢?

浙公网安备 33010602011771号