4 基本活动库

活动是组成工作流的基本块.Windows Workflow的基本活动库包含了一些都通用的工作流活动.功能涉及流程控制,事务管理,本地通信,Web Services以及其他等等.这些活动都在工具箱中可以找到.有一些活动比较简单,比如CodeActivity,它的工作就是执行一段代码.而有的活动就比较复杂,如PolicyActivity可以以正向链法(forward chaining)来估算优先级. 通过使用这些基本活动,我们可以构建强大的工作流. 

接下来让我们逐个分析这些基本活动的基本功能吧.

4.1 基本活动

基本活动库中的这些活动模拟了几乎所有编程环境中的主要操作,比如条件分支,循环还有子活动组.接下来,让我们以之前一直出现在范例中的活动开始漫游- CodeActivity.

4.1.1 CodeActivity

Code活动只有一个值得注意的功能-ExecuteCode事件.如果我们没有指定这个事件的处理程序, Code活动就不会通过验证.在工作流设计器中,我们可以双击Code活动,Visual Studio会自动创建并分配事件处理程序-我们所需要做的只是编写其中的代码实现而已.下面的代码演示了利用ExecuteCode事件在控制台中输出一条消息:

private void codeActivity1_ExecuteCode(object sender, EventArgs e)

{

Console.WriteLine(
"Hello, world!");

}


下面的截图中, 在Code活动的右上角有一个红色惊叹号图标,说明这个活动没有通过验证,因为我们没有为其指定ExecuteCode 事件.我们可以点击红色惊叹号来查看验证失败的原因.



虽然在工作流中执行自定义代码非常有用,但是实际上Code活动应该尽量少使用.我们应该将这些代码封装到自定义活动中.自定义活动可以拥有属性,而且可以重复使用.

而且,许多活动在执行时都会触发事件.有时候,我们完全可以不使用Code活动,仅仅通过在这些事件中编写代码就可以达到预期的效果.

4.1.2 IfElseActivity

IfElse活动和C#中的if…else语句很像,它的内部是一个或更多的IfElseBranch(分支)活动,每个分支活动都有一个Condition(条件)属性.除最后一个分支之外,其它分支的Condition属性都是必须设置的.

IfElse活动从左向右来判断子分支的条件并执行第一个满足条件的分支.如果所有分支的条件都不满足,就都不会被执行,在这种情况下,如果最后一个分支没有设置条件,它将会被执行.

我们可以通过右键单击IfElseActivity并选择添加分支(AddBranch)来添加一个分支,也可以通过右键单击分支并选择删除(Delete)来删除分支删除一个分支.



分支的Condition属性可以设置为声明性规则条件(Declarative Rule Condition,设计器会将其以XML格式保存到一个外部的.rules文件中),也可以设置为代码条件(Code Condition,即一个事件处理程序).如果我们设置Condition为声明性规则,我们可以从属性面板启动规则条件编辑器(Rule Condition Editor)然后输入相应的表达式.假如工作流有一个整型的属性Sales,我们可以输入这样的表达式:

This.Sales > 10000

如果我们设置Condition为代码条件,那么我们可以编写这样的代码: 

private void checkSalesAmount(object sender, ConditionalEventArgs e)

{

e.Result 
= Sales > 10000;

}

IfElse活动会触发事件来判断条件是否满足.我们可以将判断条件的结果赋给事件参数(e)的Result属性.另外,在属性面板,我们可以指定代码条件的名称(例如checkSalesAmount).

4.1.3 WhileActivity

和IfElse活动相似的是While活动也有一个Condition属性,而且同样可以被设置为声明性规则条件或代码条件. While活动会在每次循环之前判断其条件是否满足.如果满足,WhileActivity将一直运行.



和IfElse不同的是,WhileActivity只可以拥有一个子活动.当然,这个限制并不会妨碍我们在循环内使用多个活动,请接着往下看. 


4.1.4 SequenceActivity

Sequence活动是一个复合活动,也就是说Sequence活动可以管理一个或多个子活动.这些子活动轮流执行,直到最后一个子活动完成.我们知道WhileActivity只允许包含一个子活动,我们就可以把Sequence活动作为其子活动,然后在Sequence活动中添加更多的子活动.While活动每循环一次,Sequence的所有子活动都会被执行.

4.1.5 SuspendActivity

当工作流遇到错误而需要手工干预时,我们可以使用Suspend活动来暂时挂起工作流. Suspend活动有一个字符串类型的Error属性.

宿主可以订阅工作流Runtime的WorkflowSuspended事件并通过其事件参数(WorkflowSuspendedEventArgs类型)的Error属性来获取错误信息. WorkflowSuspended事件参数还提供了一个WorkflowInstance属性.宿主可以使用WorkflowInstance类的Resume方法来使工作流恢复执行,也可以使用Terminate方法使工作流悲惨的提前结束.

4.1.6 TerminateActivity

和Suspend活动相似,Terminate活动也可以挂起工作流.不同的是,使用Terminate挂起的工作流是无法被宿主恢复执行的(从单词含义来理解会更加容易:Suspend,暂停;Terminate,终止).如果工作流已经执行到不可能继续(也不可能恢复)的地步,就让我们用这个活动来结束它吧. 

Terminate活动也有一个字符串类型的Error属性.宿主可以订阅Runtime的WorkflowTerminated事件.事件处理程序参数(WorkflowTerminatedEventArgs类型)的属性Exception(WorkflowTerminatedException类型)则包含了错误信息.

如果想要指定引发WorkflowTerminated事件的异常类型,应该使用Throw活动.Throw活动可以使工作流捕捉到异常并继续执行,而Terminate活动则总是抛出异常并终止工作流.

4.1.7 ThrowActivity

Throw活动和C#中的Throw语句类似-它的作用是抛出一个异常.我们可以在Code活动的ExcuteCode事件中编写代码来抛出异常,那么为什么要使用Throw活动呢? 因为Throw活动不仅可以抛出异常,还可以在工作流模型中构造一个显式的异常块.

如果异常没有被处理并且传播到工作流之外,那么WF Runtime将捕获它,接着终止工作流并抛出WorkflowTerminated事件.Runtime会在WorkflowTerminated事件的参数中包含异常的信息.Throw活动的Fault属性可以引用要抛出的异常类型.我们可以绑定Fault属性到工作流的域或其它活动的属性.

我们还可以使用FaultType属性来描述并约束待抛出异常的类型.如果FaultType没有被设置,Throw活动可能抛出任何类型的异常.

4.1.8 InvokeWorkflowActivity

InvokeWorkflow活动可以异步的执行另一个工作流.因为执行过程是异步的,所以我们无法获取另一个工作流的输出参数,但我们可以和宿主建立额外的通信机制来获取其输出.

在设计器中,我们设置InvokeWorkflow的TargetWorkflow属性来引用想要执行的工作流类型,我们可以选择项目内或引用程序集的工作流类型.设置了目标类型之后,属性面板中就会列出目标类型的工作流参数,我们可以将其与当前工作流的域或属性绑定.在开始第二个工作流之前, InvokeWorkflow活动将触发Invoking事件,可以在此事件内编写代码来初始化参数.

InvokeWorkflow活动允许我们独立的执行一个工作流.举例来说,一个软件bug跟踪工作流在每次新的build到达时都可能要进行好几中不同类型的测试,而其中每一种测试都可能被设计为一个工作流.

4.1.9 ParallelActivity

Parallel活动允许多个活动同时执行,但Parallel活动并没有采用多线程机制,在工作流内部只有一个线程执行.

看起来好像有点复杂,这样说吧,假设一份文件需要公司里三位领导的批准:CEO,CTO和CFO.宿主会把这份文件递交给三位老大,这就是Parallel活动的”并行”.领导审批之后会触发审批相应的事件(领导们很难在同一时刻审批,所以Parallel活动并不是真正的并行),我们知道HandleExternalEvent活动可以等待并处理本地通信服务的事件,所以我们还需要三个HandleExternalEvent活动.

接下来编写一个顺序工作流来实现上面的需求,我们假设CEO的审批最先到达,然后是CTO,最后是CFO.只是这样的话在CEO还没有作出决定之前CTO和CFO只好干等.

如果审批的顺序并不重要的话,通过软件来收集审批结果就会更有效率.下图中的Parallel活动同时监听了三个事件.哪位老大的审批最先送达,工作流就处理哪个事件,同时还等待另外两个事件.只有当所有的分支都完成后Parallel活动才会结束.




4.1.10 DelayActivity

Delay活动会初始化一个计时器并等待它到期. Delay活动经常被用来模拟超时, 当计时器到期后工作流引擎还会继续执行.它的TimeoutDuration属性的值是TimeSpan类型,表示需要等待的时间.我们可以在设计器中初始化这个属性,也可以以编程的方式在InitializeTimeoutDuration事件中更改它的值:

private void delayActivity1_InitializeTimeoutDuration(object sender, EventArgs e)



DelayActivity delay 
= sender as DelayActivity; 

if (delay != null



// a 5 second time span 

delay.TimeoutDuration 
= new TimeSpan(005); 

}

}

Delay活动经常被用在Listen活动的内部.

4.1.11 ListenActivity

和Parallel活动相比,Listen活动也可以包含多个分支.但不同的是Listen活动的目的仅仅是完成一个分支.Listen活动的分支是EventDriven活动,EventDriven活动的特点是它必须以事件的到达为其开始的条件(即EventDriven活动的第一个子活动必须实现IEventActivity接口).我们将和状态机工作流一起介绍EventDriven活动的更多细节.

再继续前面关于CEO,CTO和CFO的例子,上一节中,工作流在收集齐三位领导的三分审批之后才会继续.如果我们只需要一份审批,就应该用Listen活动来替换Parallel活动.当一份审批到达时,Listen活动就会执行相应的分支事件并取消其它分支的执行.

之前曾提到,我们可以使用Delay活动来模拟超时.下图正是采用了这种搭配.如果在到期之前仍然没有事件到达,我们就可以执行一个可选的操作,比如给领导发送Email来提醒他审批,或者直接做一个默认的审批处理,就当领导默认了!

4.1.12 EventHandlingScopeActivity

和Listen活动类似,EventHandlingScope活动也可以包含多个等待事件的分支.我们可以在EventHandlingScope活动的右键菜单中选择查看事件处理程序(View Events)来查看这些分支.这两个活动主要的区别是EventHandlingScope在其主要子活动未执行完之前会一直监听其余的事件(主要子活动即默认视图显示的内容, EventHandlingScope活动包含一个主要子活动,错误处理程序和取消处理程序).

假如工作流需要在30分钟内统计员工的投票.我们应该设置EventHandlingScope的主要子活动为Delay活动,并让它延时30分钟.然后我们把处理事件的活动添加到事件分支中来监听投票的结果.这个活动会一直处理投票直到Delay活动完成(即所谓的”过期不候”). 


4.1.13 SynchronizationScopeActivity

我们知道,Parallel活动会同时执行它的每个分支, SynchronizationScope活动则正好相反,它的作用是阻止将要同时执行的活动,并让它们一个接一个的执行.

要做到这一点,我们需要向SynchronizationScope活动的SynchronizationHandles属性中添加同步句柄(或者理解为互斥标记), SynchronizationHandles属性是一个字符串集合.如果多个可能同时执行的SynchronizationScope活动的SynchronizationHandles属性中包含相同的同步句柄,那么这几个SynchronizationScope活动就不会同时执行.每当一个SynchronizationScope活动完成,相同的同步句柄就会被释放,接着刚才一直出于等待状态的其它实例中会有一个获得此同步句柄然后开始执行.

如果没有没有设置SynchronizationHandles属性, SynchronizationScope活动的功能就不会发挥,这时的SynchronizationScope活动就相当于一个Sequence活动了.

4.1.14 ReplicatorActivity

Replicator活动和While活动类似,而且更加复杂.通过设置其ExecutionType属性,Replicator可以处理顺序或者并行的数据集合.回想一下我介绍Parallel活动时的范例,三位领导审批之后我们的工作流才能继续执行.但是如果我们并不知道需要处理多少个事件呢?需要处理的数目只有Runtime在运行时才能获取.这种情况下,最适合使用Replicator活动.比如投票这种场景,Replicator活动就可以创建所需数目的事件监听器.

Replicator会去处理其InitialChildData属性中的数据对象列表中的数据,每处理一项,就会创建一个子活动的副本,直到其所有子活动都完成;此外,Replicator活动还有一个UntilCondition属性,Replicator会在开始前和每完成一个子活动时都去判断这个属性的值.如果返回true,Replicator就会停止(而且会丢弃尚未操作的子活动).和WF中其它的条件一样,UntilCondition也可以设置为规则条件或者代码条件.

也可以这样理解,Repicator活动只能包含一个子活动,在其运行时,会取出InitialChildData属性中的所有数据,然后为每一个数据创建一个子活动的副本.

Replicator还会触发了一些有用的事件,包括Initialized,Completed,ChildInitialized和ChildCompleted.我们可以ChildInitialized事件中为子活动副本指定需要的数据.

4.2 本地通信事件

让工作流和外部世界进行交互也并不是什么难事,因为WF本身就内置了一些活动来完成这样的工作.

这一节我们来讨论一下可以和宿主进程提供的本地服务进行通信的活动们

为了能让本地通信可以工作,我们需要以.NET接口的形式定义一个合同.在这个接口中会包含一些方法和事件,工作流可以通过这些方法来调用本地服务,而本地服务则可以触发这些会被工作流处理的事件.

还是关于bug跟踪的例子.有时候我们需要更详细的描述一个bug,比如一张截图.工作流可以向宿主索取这种额外需要的文档.也许宿主会自动上传文档,但实际上更多的情形是,宿主会提醒一个用户:”Hi,您需要提供bug的更详细信息!”.接着,工作流会等待图片被上传(或许几秒钟,或许几天).图片上传完毕后,宿主可以通过一个事件来通知工作流.下面的接口定义了一个服务合同. ExternalDataExchange特性将接口标识为本地通信服务,而且凭借这个标识,Runtime还会将这个接口识别为服务合同.

[ExternalDataExchange]

interface IBugService

{

bool RequestUpload(Guid id, string userName);

event EventHandler UploadCompleted;

}

接下来介绍可以和这个接口交互的两个活动: CallExternalMethod和HandleExternalEvent.

4.2.1 CallExternalMethodActivity

CallExternalMethod活动可以调用本地服务上的方法.我们需要做的仅仅是设置这个活动的属性: 



首先应该设置的是InterfaceType属性,设计器会通过我们设置的接口来自动发现服务中所有可用的方法.一旦我们把InterfaceType设置为之前定义的接口,我们就可以为MethodName属性选择需要调用的方法.设计器还会在属性面板中列出该方法需要的参数.我们可以将这些输入参数和方法的返回值绑定到工作流的域或属性.譬如图中的_newBug就是工作流的一个域.

为了使CallExternalMethod活动工作,我们需要在工作流Runtime中添加一个ExternalDataExchangeService,然后再向ExternalDataExchangeService中添加一个本地服务BugFlowService(BugFlowService类实现了IBugService接口).

WorkflowRuntime workflowRuntime = new WorkflowRuntime();

ExternalDataExchangeService dataService 
= new ExternalDataExchangeService();

workflowRuntime.AddService(dataService);

BugFlowService bugService 
= new BugFlowService();

dataService.AddService(bugService);

CallExternalMethod活动包含一个MethodInvoking事件,这个事件会在活动调用外部方法之前触发,我们可以利用这个事件来动态的操作这些参数:

private void callExternalMethodActivity1_MethodInvoking(object sender, EventArgs e)

{

if (string.IsNullOrEmpty(_newBug.NewBug.Description))

{

_newBug.NewBug.Description 
= " 这家伙太懒了,什么都没留下.";

}

}

4.2.2 HandleExternalEventActivity

像CallExternalMethod活动一样,HandleExternalEvent活动也有一个必须设置的InterfaceType属性.当我设置这个属性之后,我们就可以设置EventName属性了:


如果从本地服务而来的事件一直没有到达HandleExternalEvent的话,它就不会完成.假如事件有可能永远不会到达,或者事件需要过一段时间之后才会到达,那么最好把这个活动置于Listen活动内部.我在之前介绍过,Listen活动可以有多条分支,我们可以把Delay活动放到其中一条分支中来模拟超时.
Roles属性可以绑定到WorkflowRoleCollection对象并且允许Runtime执行基于角色的授权检查. WorkflowRoleCollection对象是一个集合,它的成员都派生自WorkflowRole抽象类,WF提供了两个WorkflowRole的实现:ActiveDirectoryRole类和WebWorkflowRole类,分别对应于AD和ASP.NET 2.0角色提供程序.Runtime会用集合中的角色来验证传入的用户标识,如果验证失败,Runtime会抛出WorkflowAuthorizationException异常. 

4.2.3 活动生成器

Windows Workflow包含了一个命令行工具来执行Windows Workflow Communications活动生成器:wca.exe.
我们可以向wca.exe传递.NET程序集(.dll或.exe)的路径,这个工具会在程序集中寻找有ExternalDataExchange标识的接口.找到之后,它就会生成专门的自定义活动,可以执行接口中的方法和处理接口中的事件.

将wca.exe生成的代码作为Workflow ActivityLibrary项目的一部分进行编译之后,添加到工具箱里,我们就可以像使用其它基本活动一样使用wca.exe生成的活动了.
自定义活动不仅包含InterfaceType以及EventName或MethodName属性,还包含所有通信所需的参数.

4.3 错误处理

Fault,故障,现在官方已经将其翻译为错误,那么以后的随笔中我也就采用官方的翻译吧.

错误处理也属于流程控制的一部分,这一节我来介绍一下有关错误处理的活动.错误是指在工作流执行期间发生的异常.我们可以使用错误处理程序来捕捉这些异常并尝试做一些”修复”工作.我们可以补偿提交的事务,或者发送Email来警告管理员并等待丢失的数据被修复.

我们最好在工作流中对可能发生的错误进行处理,如果工作流抛出了一个异常,而我们又不知道如何去处理,那么只能等Runtime终止工作流.

4.3.1 FaultHandlersActivity

FaultHandlersActivity并不是列在工具箱中等我们使用的活动.在工作流的错误处理程序视图(fault handlers view)中,默认只有一个空白的复合活动,这就是FaultHandlersActivity.而且不只是工作流, 许多复合活动(如While,Listen,Sequence和TransactionScope…)都可以通过错误处理程序视图来可以处理子活动中的错误.

在工作流或活动的右键菜单中选择查看错误(View Faults),或者点击设计器底部的第三个按钮就可以转到错误处理程序视图.我们可以在这个视图中使用FaultHandler活动.




4.3.2 FaultHandlerActivity

FaultHandler活动很好理解,因为它和C#和VB中的catch语句非常相像. FaultHandler可以捕捉异常并执行一些操作.在错误处理程序视图中,我们可以从工具箱里拖放多个FaultHandler活动到设计器图板中,每个FaultHandler都可以包含自己的子活动.我们可以添加多个FaultHandler来处理不同类型的错误(类似catch语句).

FaultHandler活动有一个FaultType属性,用来表示需要捕捉的异常类型.如果将FaultHandler设置为System.Exception,我们就可以捕捉所有的CLS-compliant异常.处理程序会捕捉所有FaultType类型的异常,或所有继承自FaultType的异常.而FaultHandler活动的另外一个属性-Fault属性则可以将异常绑定到工作流的域或者属性中. 

Runtime会从左到右依序查找FaultHandler,如果第一个FaultHandler的FaultType是System.Exception,则此FaultHandler就会捕捉所有的异常,这样做的结果就是Runtime不再需要其它的FaultHandler了(也类似catch语句).
posted on 2008-09-23 17:20  晃晃悠悠  阅读(321)  评论(0)    收藏  举报