《WF编程》系列之40 – 自定义活动:活动的执行

在Windows Workflow中,活动的状态共有六种。这些状态通过ActivityExecutionStatus枚举来表示:Initialized、Executing、Closed、Canceling、Compensating和Faulting。所有活动都从Initialized状态开始,并以Closed状态结束。下图描绘了状态之间可能进行的转换:

上图中有两个地方比较重要,我们需要特别注意一下。首先,只有当活动通知了工作流Runtime它已经完成了执行过程后,该活动才会转换到Closed状态,这是活动转换到Closed状态的唯一途径。向Runtime发出通知的方法是活动通过在某一个虚拟方法中返回ActivityExecutionStatus.Closed(这些虚拟方法稍后将会进行介绍)。

而其余的状态转换则都会执行活动的一个相应的虚拟方法。例如,当活动进入Executing状态时,Runtime就会调用活动的Execute方法;而当活动处于Canceling状态时,Runtime就会调用Cancel方法。方法及其对应的状态如下表所示:

方法 状态
Initialze Initialized
Execute Executing
Cancel Canceling
HandleFault Faulting
Compensate Compensating

Excute、Cancel、HandleFault和Compensate方法都会返回ActivityExecutionStatus。而且在任何方法中返回ActivityExecutionStatus.Closed都会导致活动转换到Closed状态。正因如此,这些方法的返回值是不可能被其它转换所使用的。举例来说,从Execute方法中返回ActivityExecutionStatus.Canceling会导致活动被转换到Canceling状态 – WF Runtime会抛出异常。活动一旦进入某一状态后,就只有两条路可以走,一是保持在当前状态,二是通知Runtime已经执行完成,让WF Runtime来组织其余的转换。

另外一个需要注意的地方是一旦活动到达了Closed状态,它就永远都不可能转换到Executing状态了。我们知道,Windows Workflow将活动视为一个工作单元,那么当一个活动关闭了,就表示它已经完成了。如果仍然继续转换,Runtime就会将这个工作单元转换到Compensating状态,但是活动永远都不可能再次初始化或者执行了。

再来另外一个问题,如果我们编写了一个复合活动,那么我们要如何管理其子活动的执行过程呢?接下来我们就把注意力从执行状态转移到执行上下文。

5.6.1 执行上下文(Execution Context)

下面是我们之前写过的自定义活动Execute方法,快速的回顾一下:

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
Console.WriteLine(Text);
return ActivityExecutionStatus.Closed;
}

在这段代码中,Execute方法返回了ActivityExcutionStatus.Closed来告诉工作流Runtime它完成了执行过程。观察这个方法,我们会发现有一个类型为ActivityExecutionContext的传入参数, ActivityExecutionContext(AEC)代表活动的执行环境,也就是我们说的执行上下文。AEC对象不仅是通往WF Runtime内部服务的桥梁(它提供了GetService方法),而且提供了一些方法来安排活动的执行过程(ExecuteActivity、CloseActivity、CancelActivity)。下面是AEC的类图:

当我们需要管理子活动时,AEC就派上大用场了。

5.6.2 自定义复合活动

重写了Execute方法后,我们就可以管理子活动的执行过程。通过使用AEC,我们就可以和WF Runtime一起协调管理子活动的执行过程。我们通过调用AEC的ExecuteActivity方法来执行子活动,而不是直接调用子活动的Execute方法,因为Windows Workflow Runtime其实并不知道这个活动在做什么。

WF Runtime通过AEC来安排活动的执行过程并执行规则。举例来说,Runtime不会让我们使用ExecuteActivity来执行已经处于Closed状态的活动。这样的操作并不属于正确的状态转换,所以会抛出一个异常。

如果我们的自定义活动从CompositeActivity类继承而来,那么它的Execute方法将会遵循以下模式:

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
_currentIndex = 0;
Activity child = EnabledActivities[0];
child.Closed += new EventHandler(child_Closed);
executionContext.ExecuteActivity(child);
return ActivityExecutionStatus.Executing;
}

在这段代码里,我们从父类提供的EnableActivities集合中找到了第一个子活动,并为它注册了Closed事件。然后我们通过调用ExecuteActivity方法来要求WF Runtime去安排活动的执行。这时,我们必须返回Executing状态,因为我们需要等所有的子活动都执行完毕后才能关闭这个活动。返回Executing状态通常是因为这个活动需要等待一个事件被触发。在本例中,我们需要等待第一个子活动执行完毕,但我们也可能是为了等待消息到达队列。

当子活动触发了它的Closed事件后,我们就可以继续上面的模式来获取下一个子活动(如果有的话),订阅它的Closed事件并安排它去执行。

void child_Closed(object sender, ActivityExecutionStatusChangedEventArgs e)
{
ActivityExecutionContext context = sender as ActivityExecutionContext;
e.Activity.Closed -= child_Closed;
_currentIndex++;
if (_currentIndex < EnabledActivities.Count)
{
Activity child = EnabledActivities[_currentIndex];
child.Closed += new EventHandler
(child_Cslosed);
context.ExecuteActivity(child);
}
else
{
context.CloseActivity();
}
}

在获取下一个可运行的子活动之前,我们要退订这个已经完成的子活动的Closed事件。如果我们找不到可用的活动来运行,我们就通过调用AEC的CloseActivity事件来通知Runtme我们已经执行完毕。

上边的代码按顺序遍历集合中的子活动 – 就像SequenceActivity的运行方式一样。但如果我们把自定义活动放到循环中,并循环多次,我们就必须使用不同的方法了。记住,每个活动只有一次机会到达Executing状态。如果我们需要多次执行一个活动,我们就需要创建新的执行上下文。

这时难免引入一个有趣的问题,sWhile活动是如何多次执行相同的活动呢?事实上,While活动在每一次执行它的子活动时都生成了新的ActivityExecutionContext对象。这时原始的子活动就变成了模板活动,While的每一次循环都会把这个模板复制一份。这个过程如下图所示:

While活动的执行模式类似于下面的代码:

ActivityExecutionContextManager manager;
manager = executionContext.ExecutionContextManager;
ActivityExecutionContext newContext;
newContext = manager.CreateExecutionContext(EnabledActivities[0]);
newContext.Activity.Closed += new EventHandler(Activity_Closed);
newContext.ExecuteActivity(newContext.Activity);

我们获取上下文管理器(context manager)并利用它的CreateExecutionContext方法为子活动创建新的上下文(需要将活动作为参数传递给它),然后利用它来安排活动运行。CreateExecuteContext方法会将参数中的活动克隆一份。这是一种深层次的克隆,所以如果EnabledActivities[0]如果是复合活动的话,这个方法还会克隆它的子活动。

那么补偿是如何管理的呢?因为在Windows Workflow中,每个活动都表示一个工作单元,如果While活动的子活动需要执行补偿,它所表示的工作单元内的所有活动都会被强制克隆并执行补偿。如果While活动错误的多次执行同一个活动,这个工作单元就会被丢失,补偿也就不会执行了。所以不要忘记,在创建可循环的自定义控件时要使用CreateExecutionContext方式。

小结

这一章我们讨论了两种在Windows Workflow中创建自定义活动的技术。

使用组合法,我们可以快速创建可重用的工作流逻辑单元。虽然自定义活动内部形成了黑盒,我们还可以通过使用依赖属性和活动数据绑定来曝露更多细节给工作流设计人员。

继承法是第二种创建自定义活动的方法。通过使用继承法,我们重写了活动的Execute方法并可以完全控制它的执行逻辑。继承法允许我们创建新的控制流方式和新的执行语义。而且我们可以添加活动组件来执行验证或者提供自定义设计器行为(活动组件通过特性来与活动关联)。

9 Comments

发表评论

电子邮件地址不会被公开。 必填项已用*标注