WF 从入门到精通(第十一章):并行活动

学习完本章,你将掌握:

1.理解在工作流环境中Parallel 活动是怎样执行的,并且懂得如何使用它们

2.并行执行路径中的同步数据存取和临界代码区

3.使用ConditionedActivityGroup 活动去执行根据条件表达式判断执行路径的并行活

在本书中截止目前为止,我们仅仅处理过顺序业务流程。如活动A 执行后转到活动B 的

执行等等。我们还没看到过并行执行路径和由此通常伴随而来的错综复杂的情况。在本章中,

我们将看看并行活动的处理过程,以及看看怎样对横跨并行执行路径的共享信息进行同步存

取。


使用Parallel活动

当你用完某样东西需去杂货店买的时候,通常都可能只有一条结帐流水线。所有的顾客

都必须通过这条唯一的结帐线来付款。在那些罕见的情况下,当有两个或更多的结帐线开放

后,顾客和杂货结帐的速度会更快,因为他们是以并行的方式结帐的。

在某种意义上,工作流活动也是如此。有时,你不能以混乱的方式甚至更糟糕的随机的

顺序来执行特定的活动。在这些情况下,你必须选择一个Sequence 活动来容纳你的工作流。

但在其它时候,你可能需要设计在同一时间(或者如我们将看到的,几乎是在同一时间)能

够执行多个处理过程的流程。对于这些情况,Parallel 活动是一个选择。

Parallel 活动是一个组合活动,但它只支持Sequence 活动作为它的子活动。(当然,

你可自由地把你想使用的任何活动放到该Sequence 活动中。)它至少需要两个Sequence 活

动。

子Sequence 活动并没有在单独的线程上执行,因此Parallel 活动不是一个多线程活动,

相反,那些子Sequence 活动在单一的一个线程上执行。WF 会只对一个Parallel 活动执行路

径中的某一单独的活动进行处理,直到该活动完成才会切换到另一个并行执行路径中的一个

单独的活动。也就是说,在某个分支内的某个单独的子活动完成后,才能安排其它分支中的

另一个单独的子活动去执行(译者注:每个单独的子活动是Parallel 活动执行的最小单位)。

各个并行活动间真正的执行顺序是无法保证的。

这样的结果是并行执行路径不会同时被执行,因此它们并不是真正意义上的多线程中的

并行执行。但是,它们执行时就像是在并行操作,并且你也可这样看待它们。把Parallel 活

动看成是真正意义的并行过程是最明智的:你可像对待任何多线程下的处理过程来对待并行

活动。

注意:假如你需要强制指定并行执行路径间的顺序,可考虑使用SynchronizationScope

活动。本章晚些时候我将对它进行演示。

在此时有个值得关注的问题:“有Delay 活动会怎么样呢?”正如你知道的,顺序工作

流中的Delay 活动会停止执行指定的TimeoutDuration 时间间隔。那这会停止Parallel 活动

的处理过程吗?答案是不会。延时会导致特定的顺序工作流路径将被停止,但其它并行路径

会正常地继续进行处理。

考虑到我所发出过的所有这些多线程警告,你或许会认为使用Parallel 活动是一个挑

战。事实上,它非常容易使用。它在工作流视图设计器中呈现出来的样式和Listen 活动(该

活动在第10 章“事件活动”中讨论过)非常相像。和EventDriven 活动不同的是,你将会在

它里面找到Sequence 活动,除此之外,表现出来的可视化界面都是相似的。我们这就创建一

个简单的例子来演示一下Parallel 活动。


创建一个带有并行执行过程的新工作流应用程序

1.为了快速地演示Parallel 活动,本例子使用的是一个基于控制台的Windows 应用程序。

我们需要下载本章源代码,源代码中包含有练习版和完整版两个版本的解决方案项目,完整

版中的项目可直接运行查看运行结果,我们在此使用Visual Studio 打开练习版中的解决方

案项目文件。

2.在Visual Studio 打开了ParallelHelloWorld 解决方案后,找到Workflow1 工作流并

在工作流视图设计器中打开它。

3.从工具箱中拖拽一个Parallel 活动到设计器界面上。

image.png

4.在Parallel 活动放到工作流视图设计器界面上后,它会自动地包含一对Sequence 活

动。在左边分支的Sequence 活动中拖入一个Code 活动,在属性面板上指定它的名称为msg1,

并在它的ExecuteCode 属性中输入Message1。

image.png

备注:尽管在Parallel 活动中的并行执行路径不能少于两个,但它并不会阻止你添加更

多的并行执行路径。假如你需要三个或更多的并行执行路径,你可简单地把多个Sequence 活

动拖拽到设计器界面上并把它们放到Parallel 活动中。

5.在Visual Studio 中切换到代码视图界面下,在Message1 事件处理程序中添加下面的

代码,然后回到工作流视图设计器界面上来。

Console.WriteLine("Hello,");

6.拖拽第二个Code 活动到左边的Sequence 活动中,把它放到Code 活动msg1 的下面。

该Code 活动的名称命名为msg2,并在它的ExecuteCode 属性中输入Message2。

image.png

7.当Visual Studio 为你切换到代码视图界面后,在Message2 事件处理程序中输入下面

的代码,然后回到工作流视图设计器界面上来。

Console.WriteLine(" World!");

8.现在拖拽第三个Code 活动到右边的Sequence 活动中。它的名称命名为msg3,在它的

ExecuteCode 属性中输入Message3。

image.png

9.在Message3 的事件处理程序中添加下面的代码:

Console.WriteLine("The quick brown fox");

10.回到工作流视图设计器界面上来,拖拽第四个Code 活动并把它放进右边的Sequence

活动中,具体位置是在你前一步所添加的Code 活动的下面。它的名称命名为msg4,它的

ExecuteCode 属性的值设置为Message4。

image.png

11.在Message4 的事件处理程序中输入下面的代码:

Console.WriteLine(" jumps over the lazy dog.");

12.现在工作流的设计工作就完成了,你需要在ParallelHelloWorld 应用程序中添加对

ParallelFlow 项目的项目级引用。

13.在ParallelHelloWorld 项目中打开Program.cs 文件,找到下面的一行代码:

Console.WriteLine("Waiting for workflow completion.");

14.在你找到的上述代码下,添加下面的代码以便创建工作流实例:

// Create the workflow instance.

WorkflowInstance instance =

workflowRuntime.CreateWorkflow(typeof(ParallelFlow.Workflow1));

// Start the workflow instance.

instance.Start();

15.编译本解决方案,修正所有的编译错误。

16.按下F5 或者Ctrl+F5 运行本应用程序。注意,为了能看到输出结果,你应该在

Program.cs 中(在Main 方法内)设置一个断点。

image.png

和你在上面的图片中看到的一样,输出的信息是杂乱的。假如左边Sequence 活 动无干

扰地一直运行到结束,则在输出的结果中,“World!”就在“Hello,”的下面。假如右边的

Sequence 活动没有被干扰,则会输出下面由 Western Union 发明的包含了所有26 个字母,

用来测试电传打字机是否正常的一句话:“The quick brown fox jumps over the lazy dog”。

但是,这是好事,因为从这些Code 活动中输出信息的杂乱顺序正好指出了并行活动的执行方

式。

假如你看仔细些,你将会看到各个Code 活动都要一直运行到完成后才会转去执行另一个

Code 活动。你或许也会注意到左边的Sequence 活动在右边的Sequence 活动前面启动。当前

该Parallel 活动的执行顺序和伪随机数的生成结果是类似的。假如你使用相同的随机种子

值,产生的随机数实际上并不是随机的,它们是以可预期的方式生成的,在本Parallel 活动

中,也总是以这样的方式来执行的,从左到右,从上到下。它的执行顺序也是可预期的。

但是,不要把这样的现象和你的业务逻辑联系起来。就如我前面谈到的,你应当认为

Parallel 活动的执行方式是真正并行的。你必须假定并行执行路径是以随机的顺序执行的,

即使可能个别的活动总是在切换执行上下文前结束。假如WF 打破该契约(规则),那并非为

多线程操作所设计的活动内部的代码也会被中断,这可不是什么好事。

这就自然而然地产生了一个问题:你怎么协调并行执行路径,为什么要这样做呢?这个

问题问得非常好,这把我们带入了下面的话题:同步。


使用SynchronizationScope活动


任何曾经写过多线程应用程序的人都知道线程同步是一个很关键的话题。现代Windows

操作系统使用任务调度程序来控制CPU 上线程的执行,任务调度程序在任何时候都可移走一

个正在执行的线程,假如你疏忽,甚至可能在一个关键操作过程中发生这种情况。

当你写基于Windows 的应用程序时,你有许多的多线程手段可以利用:如互斥、内核事

件、临界区、信号量等等。但最终有两件事必须得到控制:一是临界代码的完成过程中不能

进行线程的切换,还有就是共享内存的存取(例如包含有volatile 信息的变量)。

备注:这里使用volatile 是有一定含义的。它意味着数据改变,对于任意的时间段都不

能保证仍然还是某一特定值。

WF 通过提供SynchronizationScope 活动的使用来解决上面提到的两种情况。和传统的

多线程编程所要做的工作相比,你不需要去使用许多不同的手段(也就是说,你不需要去理

解每种手段所使用的场合及使用方法)。相反,这一个活动的作用就是处理上面提到的两种

情况:完成临界代码区的执行过程及volatile 内存的存取。

当你把一个SynchronizationScope 活动放到你的工作流中的时候,WF 会保证在该执行

上下文切换到其它的并行路径以前,该组合活动(指SynchronizationScope 活动)内部的所

有活动都将全部运行完成。这意味着你能在SynchronizationScope 内部访问所有的volatile

内存和完成临界区代码的执行。

SynchronizationScope 使用的机制和互斥(mutex)相似。(事实上,它会作为一个临

界区或者加锁执行,因为同步的范围不会跨越应用程序域。)在传统的多线程编程中,mutex

是 为互相排斥所提供的一个对象。在一定程度上,它就像是一个令牌或钥匙。换句话说,当

一个线程要求进行互斥,仅仅另一个线程并没有使用该互斥对象时才允许它 去访问这个互斥

对象。假如另一个线程正在使用这个互斥对象,第二个线程就会阻塞(等待),直到第一个

线程已经完成了它的任务并且释放了该互斥对象。

互斥对象通常只不过是“named”这样一个字符串,你也可使用任何你喜欢的字符串。但

是,多个线程互斥访问同一个互斥体必须使用同一个字符串对象。

SynchronizationScope 也有相似的特点,这通过它的SynchronizationHandles 属性来

提供支持。SynchronizationHandles 其实是一个字符串集合,它们中的每一个(字符串)的

作用是和要进行同步处理的其它的SynchronizationScope 对象建立关联。假如你没有为该属

性指定至少一个字符串,Visual Studio 也不会为你报错,但是SynchronizationScope 不会

工作。和使用互斥对象一样,所有要进行同步的SynchronizationScope 活动都必须使用相同

的SynchronizationScope 字符串。

在进入我们的例子之前,我们要回头去看看前一个示例应用程序的输出结果。看到了那

些杂乱的信息没有?我们把SynchronizationScope 活动运用到前面的示例应用程序中,来迫

使这些信息以一个恰当的顺序输出。说得更明白一点,就是我们在执行上下文环境进行切换

前,强制让临界区内的代码一直运行到完成,但我也将引入volatile 内存去演示它的工作方

式。


创建一个带有同步化并行执行方式的新工作流应用程序

1.在本实例中,你将再次使用基于控制台的Windows 应用程序,该应用程序和前一个实

例非常相似。在你下载的本章源代码中,打开SynchronizedHelloWorld 文件夹内的解决方案。

2.在Visual Studio 加载了该解决方案后,在工作流视图设计器中打开SynchronizedFlow

项目中的Workflow1 工作流,拖拽一个Parallel 活动到设计器界面上。

image.png

3.现在拖拽一个SynchronizationScope 活动到设计器界面上,把它放到左边的Sequence

活动中。

image.png

4.设置你刚才在工作流中所添加的SynchronizationScope 活动的

SynchronizationHandles 属性为SyncLock。

image.png

备注:你在SynchronizationHandles 属性中输入的文本字符串并不重要。重要的是所有

要被同步的SynchronizationScope 活动都要使用相同的文本字符串。

5.拖拽一个Code 活动到SynchronizationScope 活动中,在属性面板上指定它的名称为

msg1,它的ExecuteCode 属性为Message1。

image.png

6.Visual Studio 会为你切换到代码视图中。在Message1 的事件处理程序中输入下面的

代码:

_msg = "Hello,";

PrintMessage();

7.但是你同时还需要添加_msg 字段和PrintMessage 方法。在Workflow1 源代码中找到

它的构造器,在它的构造器下面添加下面的代码:

private string _msg = String.Empty;

private void PrintMessage()

{

// Print the message to the screen

Console.Write(_msg);

}

8.拖拽第二个Code 活动到SynchronizationScope 活动中,把它放到msg1 活动的下面。

它的名称命名为msg2,它的ExecuteCode 属性设置为Message2。

image.png

9.当Visual Studio 切换到代码视图后,在Message2 事件处理程序中输入下面的代码:

_msg = " World!\n";

PrintMessage();

10.拖拽一个SynchronizationScope 活动放到右边的Sequence 活动中。

image.png

11.为了让这个SynchronizationScope 活动和你在第4 步中插入的

SynchronizationScope 活动进行同步,在当前这个SynchronizationScope 活动的

SynchronizationHandles 属性中输入SyncLock。

12.现在拖拽一个Code 活动放到你刚才插入的这个SynchronizationScope 活动中。它的

名称命名为msg3,它的ExecuteCode 属性设置为Message3。

13.在Message3 事件处理程序中插入下面的代码:

_msg = "The quick brown fox";

PrintMessage();

14.拖拽第四个Code 活动,把它放入右边Sequence 活动中的SynchronizationScope 活

动中。它的名称命名为msg4,它的ExecuteCode 属性设置为Message4。

image.png

15.在Message4 事件处理程序中插入下面的代码:

_msg = " jumps over the lazy dog.\n";

PrintMessage();

16.工作流现在就完成了。从SynchronizedHelloWorld 应用程序中添加对该工作流的项

目级引用。

17.打开SynchronizedHelloWorld 项目中的Program.cs 文件,找到下面一行代码:

Console.WriteLine("Waiting for workflow completion.");

18.在你找到的上面一行代码的下面,添加下面的代码来创建一个工作流实例:

// Create the workflow instance.

WorkflowInstance instance =

workflowRuntime.CreateWorkflow(typeof(SynchronizedFlow.Workflow1));

// Start the workflow instance.

instance.Start();

19.编译该解决方案,纠正任何编译错误。

20.执行该应用程序。你可能需要在Main 方法中设置一个断点才能看到输出结果。假如

输出结果中显示的这两条信息还是杂乱的,你需要确认你在两个SynchronizationScope 活动

(步骤4 和步骤11 中添加)的SynchronizationHandles 属性中输入的是完全相同的字符串。

image.png

我们在本章中将介绍的最后一个活动和你看过或将看到的任何其它活动都大不一样,它

叫做ConditionedActivityGroup 活动。它具有并行和循环两方面的特征。让我们瞧瞧吧。


使用ConditionedActivityGroup(CAG)活动

简要地说,CondtionedActivityGroup 活动(通常都称作CAG)是一个组合活动,它为你

提供了一个角色,使你能对要执行的并行子活动进行调度。总的来看,它会运行到你指定的

一个条件为true 时为止,假如你没有指定这个条件,它则会运行到所有的子活动都报告它们

没有更多要去执行的任务时为止。我提到的这个条件就是CAG 的until condition。

子活动并行执行,并且只有条件满足的子活动才被执行。这个条件也就是所谓的when

condition。假如没有任何子活动满足when 条件,也就没有子活动去执行,并且CAG 活动会

结束,除非你通过设置它的until 条件强制它继续进行。假如一个或多个子活动满足了when

条件,那这些子活动将并行执行。其它没有满足when 条件的子活动将维持一个空闲状态。通

过设定要执行的子活动的when 条件,你能决定哪些子活动将去执行。

CAG 开始执行时要判断它的until 条件。假如判断结果指明要继续执行的话,则也要对

每一个子活动的when 条件进行判断。判断结果如确定要去执行,则会导致相关的子活动如期

运行。假如超过一个活动要如期执行,则执行的顺序将取决于它们被放入父CAG 活动的顺序。

在每一个子活动执行结束后,CAG 将对until 条件进行重新判定,同样的还有子活动的

when 条件。这是因为对于一个正执行的活动来说,一旦它结束,就可能影响到其它子活动的

执行顺序,甚至是整个CAG 活动。

CAG 在工作流视图设计器中的使用方式也和其它活动大不一样。它的设计器用户界面和

其它活动的插入错误处理程序的界面相似(如图11-1 所示)。假如你拖拽各个子活动到CAG

的设计器界面上,并把它们放到两个箭头图标的中间,则在两个箭头中间的矩形略低的地方

将显示出如图11-1 所示的文字:编辑。当你拖拽子活动并放到这个矩形中后,在下方的窗口

中将呈现出这些活动的图形。在图11-1 中,你看到了一个名称为setLevel1 的活动,它来自

于你即将创建的一个示例应用程序。

image.png

图11-1 ConditionedActivityGroup 活动的设计器用户界面

子活动拖进CAG 中后有两个显示模式:预览模式和编辑模式。图11-1 显示的是编辑模式。

在编辑模式中,你能为子活动设置属性,例如设置它的when 条件。当处于预览模式时,你只

能浏览子活动的设计器图片,这时在Visual Studio 中显示出的属性都是CAG 自己的。点击

图11-1 中的“编辑”文字左边的小正方形按钮,或者双击主CAG 窗口中的子活动,你可在编

辑和预览模式之间进行切换。

放到CAG 中的子活动都只能是单一的活动。假如在你的工作流处理过程中其中一个子活

动需要执行超过一个以上的功能,就像处理一个事件的过程中,在事件响应时去执行一个Code

活动一样,你应使用一个Sequence 活动来作为容器,把它放入CAG 中作为CAG 活动直接的子

活动。

像CAG 之类的活动的使用场合在什么地方呢?我认为它是那些很少使用的活动中的一

个,但当它适合你的处理模型时,它就会使事情变得非常的简单。

例 如,想象这样一个工作流程,需要对某些化学制品或材料的量进行监控。假如你往储

备箱中填入了过量的这些东西,则工作流会自动的把这些过多的制品或物料释放 到一个溢出

箱中。当储备箱为空或者低于一个特定的下限值时,这个工作流会检测到这个情况并发送一

条警告信息。对于其它情况,工作流继续对储备箱中的量进行 监控,但它不会产生任何动作。

把上面这些转换成CAG 后,CAG 活动会一直运行到你决定不再需要进行监控时为止。假

如储备箱变得太空,一个子活动会发出一个警告,假如制品或物料的量超过了特定的上限值,

则另一个子活动会打开溢出箱。你可使用一个包含了IfElse 活动的while 活动来实现同样的

功能,但在这个例子中CAG 是最合适的。(也可以说使用CAG 活动是更加简洁的解决办法。)

为了对CAG 进行说明示范,我已创建好了我提到的这个应用程序。TankMonitor 使用一

个工作流来监控储备箱中流体的量,它使用了一个简单的动画控件来模拟这个储备箱。图11-2

展示了这个空储备箱。

image.png

图11-2 储备箱为空时的TankMonitor 用户界面

图11-3 展示的是储备箱为半满时的情形。而图11-4 展示出了储备箱中流体过量时的情

形。和你看到的一样,储备箱下面的label 控件会为你提供任何状态提示信息。这个label

控件的提示信息完全由工作流的反馈结果控制,而不是被储备箱的滑动条控件直接进行控制。

image.png

图11-3 储备箱为半满时的TankMonitor 用户界面

图11-

image.png

图11-4 储备箱中流体过量时的TankMonitor 用户界面


在你的工作流中使用ConditionedActivityGroup活动

1.在下载的本章源代码文件夹目录中打开名称为TankMonitor 的练习项目解决方案。

2.在Visual Studio 打开了该解决方案后,在工作流视图设计器中打开Workflow1.cs 文

件。

3.拖拽一个ConditionedActivityGroup 活动到工作流视图设计器界面上。

image.png

4.下图是conditionedActivityGroup1 在Visual Studio 的属性窗口中呈现的界面。选

择UntilCondition 属性,这会显示出一个向下的箭头。点击这个向下的箭头,这会显示出列

表选择项,此处选择代码条件。

image.png

5.通过点击加号(+)展开UntilCondition 属性,在Condition 属性中输入CheckContinue。

在Visual Studio 添加了对应的事件处理程序后,重新回到工作流视图设计器上来。

image.png

6.生成该解决方案(按下F6),这将使本项目中的这个自定义活动呈现在工具箱中。现

在,需向CAG 中添加第一个子活动。从Visual Studio 工具箱中,拖拽一个自定义SetLevel

活动到工作流视图设计器界面上,把它放到CAG 活动的矩形区域中,矩形区域的具体位置在

“<”按钮的右边。如下图所示:

image.png

备 注:在CAG 的主窗口内显示的锁状图标表示子活动处于预览模式。(CAG 主窗口上方

的文字也指明了当前所处的模式。)进入编辑模式后你能对 setLevel1 的属性进行编辑,通

过点击“<”按钮右方矩形窗口中的setLevel1 活动的图标,你也能进入编辑模式并对它的属

性进行编辑。

7.我们现在就先进入CAG 的编辑模式,点击“预览”文字旁边的微型正方形按钮。这是

一个开关型的按钮,如再点击一次这个按钮,将使CAG 再次进入预览模式。

8.进入CAG 的编辑模式后,你就可通过在CAG 的主窗口中选中它的子活动,然后就可对

它的属性进行设置。我们现在点击setLevel1 活动以便激活它的属性。

image.png

9.选中setLevel1 的WhenCondition 属性,它将显示一个向下的箭头,然后从它的列表

选择项中选择代码条件。

image.png

10.展开这个WhenCondition 属性,在Condition 属性的文本框中输入AlwaysExecute。

Visual Studio 也会自动为你添加一个对应的方法并会切换到代码视图下。我们需回到工作

流视图设计器界面下,因为你还需对setLevel1 活动的多个属性进行设置。

image.png

11.在setLevel1 的Invoked 属性中输入OnSetLevel 并按下回车键。在Visual Studio

添加了对应的OnSetLevel 事件处理程序后,重新回到工作流视图设计器界面下。

image.png

12.你需要为setLevel1 设置的最后一个属性是Level 属性。选择它的Level 属性,这将

显示一个浏览(...)按钮,然后点击这个浏览按钮。这将打开一个“将‘Level’绑定到活

动的属性”对话框。然后点击绑定到新成员选项卡,点击创建属性,在新成员名称中输入

TankLevel,最后点击确定。

image.png

13.你现在需要向CAG 中放入另外一个子活动。从工具箱中拖拽一个自定义Stop 活动到

“<”按钮右边的矩形窗口中,并把它放到setLevel1 活动的右边。

image.png

14.选择stop1 的WhenCondition 属性,这将显示一个向下的箭头,从它的列表选择项中

选择代码条件。

image.png

15.点击WhenCondition 旁边的“+”,这将显示出Condition 属性。在这个例子中,你

可和setLevel1 活动共享一个Condition 属性值,因此,选中这个Condition 属性,这将显

示一个下拉列表框,然后从它显示出的列表项中选择AlwaysExecute。

image.png

16.接下来选中stop1 的Invoked 属性,在它的文本框中输入OnStop。同样,这也会添

加一个OnStop 方法。在添加了这个方法后重新回到工作流视图设计器界面上来。

image.png

17.现在准备向CAG 中添加第三个活动。这次,拖拽一个自定义UnderfillAlert 活动到

设计器界面上,把它放到CAG 中stop1 活动的右边。

image.png

18.点击underfillAlert1 活动的WhenCondition 属性,从显示出的下拉列表框中选择代

码条件。

image.png

19.点击“+”展开该WhenConditon 属性,在Condition 中输入CheckEmpty。同样,在

添加了对应的CheckEmpty 方法后回到工作流视图设计器界面上来。

image.png


20.接下来,你需要把underfillAlert1 的level 属性绑定到你在第12 步中创建的

TankLevel 属性。为此,点击level 属性激活它的浏览(...)按钮。然后点击该浏览按钮,

这就打开一个“将‘level’绑定到活动的属性”对话框。但这次,你将绑定到一个现有的属

性,因此只需从现有的属性中选中TankLevel 即可,然后点击确定。

image.png

21.underfillAlert1 活动就完全配置好了,我们现在要把最后一个活动添加到本示例应

用程序的CAG 中。拖拽一个自定义OverfillRelease 活动到设计器界面上,把它放到CAG 中

其它现有活动的最右边。

image.png

22.同样,你需要设置它的WhenCondition 属性,因此点击overfillRelease1 的

WhenCondition 属性并从它的下拉列表中选择代码条件。

image.png

23.展开WhenCondition 旁边的“+”,这就显示出它的Condition 属性。在该属性中输

入CheckOverfill。在添加了对应的CheckOverfill 方法后重新回到工作流视图设计器界面

上来。

image.png

24.和第20 步中你为underfillAlert 所做的工作一样,现在把overfillRelease1 的

level 属性绑定到TankLevel 属性上。点击level 属性,这就显示出一个浏览(...)按钮。

点击该浏览按钮,这将打开一个“将‘level’绑定到活动的属性”对话框。从现有属性列表

中选择TankLevel,然后点击确定。

25.本工作流在视图设计器界面上的设计工作就完成了,现在需要切换到Workflow1.cs

的代码视图下添加一些代码。

26.打开Workflow1.cs 源文件,在源文件中找到Workflow1 构造器。在构造器下面添加

如下的代码,主要作用是创建在启动工作流时需要用到的储备箱中容纳流体的下限值和上限

值的属性。

private bool _stop = false;

private Int32 _min = -1;

private Int32 _max = -1;

private bool _notificationIssued = false;

public Int32 TankMinimum

{

get { return _min; }

set { _min = value; }

}

public Int32 TankMaximum

{

get { return _max; }

set { _max = value; }

}

27.接下来你将看到的是CheckContinue 方法,这个方法是你在设置CAG 的

UntilCondition 属性时自动添加的。这个方法实际上是一个事件处理程序,

ConditionalEventArgs 包含了一个Result 属性,你可对其设置以决定是让CAG 继续进行处

理还是让它停止,如设置Result 为true 将使其停止,而设置为false 将使其继续进行处理。

向CheckContinue 中添加下面一行代码(_stop 是一个标志,它在OnStop 事件处理程序中被

设置):

e.Result = _stop;

28.两个CAG 活动,setLevel1 和stop1,都应当始终运行。因此在AlwaysExecute 中添

加下面一行代码:

e.Result = true;

29.找到OnSetLevel 方法,在SetLevel 事件被响应时将调用该方法。实际上储备箱中的

量是由WF 自动为你设置的,因为你把setLevel1 的Level 属性绑定到了TrankLevel 这个依

赖属性上。下面添加的代码的作用是对任何警告通知进行重置,以便在储备箱中量的新值不

再合理范围内时能让overfill 活动或underfill 活动发出它们的通知。

_notificationIssued = false;

30.Stop 事件的作用是当它触发时对CAG 的UntilCondition 进行设置以使它停止处理。

在Workflow1.cs 文件中找到OnStop 方法,并为它添加下面两行代码:

// Set the stop flag

_stop = true;

31.接下来定位到underfillAlert1 的WhenConditon 属性对应的CheckEmpty 方法。尽管

你想让CAG 每次都对它的子活动的WhenCondition 进行判断,但你并不想让通知(低于下限

值或超过上限值时)不停地发送到用户界面上,因为这将消耗过多的CPU 周期。实际上,你

只想让通知在储备箱的容量状态级别发生更改后只发出一次。下面的代码就为你做这个工作,

这些代码添加到CheckEmpty 方法中。

// If too empty, execute

e.Result = false;

if (TankLevel <= TankMinimum)

{

e.Result = !_notificationIssued;

_notificationIssued = e.Result;

} // if

32.overfillRelease1 也需要对它的WhenCondition 进行判断,因此找到CheckOverfill

方法并添加和上面相似的代码:

// If too full, execute

e.Result = false;

if (TankLevel >= TankMaximum)

{

e.Result = !_notificationIssued;

_notificationIssued = e.Result;

} // if

33.保存所有打开的文件,编译该解决方案。

34.执行该应用程序。向上或向下拉动滑动条可对储备箱进行补充或抽空进行模拟。当储

备箱中的流体超过或低于边界条件时注意观察它下面所显示的文本。

备 注:我在创建这个TankMonitor 应用程序的过程中,当添加事件处理程序时会感到它

非常像是一个基于状态机的工作流。假如在你创建顺序工作流的过程 中发现,在一个特别的

业务流程中使用一点点基于状态机的处理流程会更有用的话,CAG 或许就是一个既快速又简

便的极好的选择,这不用重新创建并调用一个独 立的基于状态机的工作流。

信不信由你,到现在你已经看到过足够多的用来解决许多和工作流任务相关的处理过程,

但这些章节讨论的话题还只是加深你对WF 的理解。

源码下载 http://files.cnblogs.com/gyche/WF%20Step%20by%20Step/Chapter11.rar


--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!



本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864