学习完本章,你将掌握:
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活动到设计器界面上。
4.在Parallel活动放到工作流视图设计器界面上后,它会自动地包含一对Sequence活动。在左边分支的Sequence活动中拖入一个Code活动,在属性面板上指定它的名称为msg1,并在它的ExecuteCode属性中输入Message1。
备注:尽管在Parallel活动中的并行执行路径不能少于两个,但它并不会阻止你添加更多的并行执行路径。假如你需要三个或更多的并行执行路径,你可简单地把多个Sequence活动拖拽到设计器界面上并把它们放到Parallel活动中。
5.在Visual Studio中切换到代码视图界面下,在Message1事件处理程序中添加下面的代码,然后回到工作流视图设计器界面上来。
Console.WriteLine("Hello,");
6.拖拽第二个Code活动到左边的Sequence活动中,把它放到Code活动msg1的下面。该Code活动的名称命名为msg2,并在它的ExecuteCode属性中输入Message2。
7.当Visual Studio为你切换到代码视图界面后,在Message2事件处理程序中输入下面的代码,然后回到工作流视图设计器界面上来。
Console.WriteLine(" World!");
8.现在拖拽第三个Code活动到右边的Sequence活动中。它的名称命名为msg3,在它的ExecuteCode属性中输入Message3。
9.在Message3的事件处理程序中添加下面的代码:
Console.WriteLine("The quick brown fox");
10.回到工作流视图设计器界面上来,拖拽第四个Code活动并把它放进右边的Sequence活动中,具体位置是在你前一步所添加的Code活动的下面。它的名称命名为msg4,它的ExecuteCode属性的值设置为Message4。
11.在Message4的事件处理程序中输入下面的代码:
Console.WriteLine(" jumps over the lazy dog.");
12.现在工作流的设计工作就完成了,你需要在ParallelHelloWorld应用程序中添加对ParallelFlow项目的项目级引用。
13.在ParallelHelloWorld项目中打开Program.cs文件,找到下面的一行代码:
Console.WriteLine("Waiting for workflow completion.");
14.在你找到的上述代码下,添加下面的代码以便创建工作流实例:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
15.编译本解决方案,修正所有的编译错误。
16.按下F5或者Ctrl+F5运行本应用程序。注意,为了能看到输出结果,你应该在Program.cs中(在Main方法内)设置一个断点。
和你在上面的图片中看到的一样,输出的信息是杂乱的。假如左边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活动到设计器界面上。
3.现在拖拽一个SynchronizationScope活动到设计器界面上,把它放到左边的Sequence活动中。
4.设置你刚才在工作流中所添加的SynchronizationScope活动的SynchronizationHandles属性为SyncLock。
备注:你在SynchronizationHandles属性中输入的文本字符串并不重要。重要的是所有要被同步的SynchronizationScope活动都要使用相同的文本字符串。
5.拖拽一个Code活动到SynchronizationScope活动中,在属性面板上指定它的名称为msg1,它的ExecuteCode属性为Message1。
6.Visual Studio会为你切换到代码视图中。在Message1的事件处理程序中输入下面的代码:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
7.但是你同时还需要添加_msg字段和PrintMessage方法。在Workflow1源代码中找到它的构造器,在它的构造器下面添加下面的代码:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedBlockEnd.gif)
8.拖拽第二个Code活动到SynchronizationScope活动中,把它放到msg1活动的下面。它的名称命名为msg2,它的ExecuteCode属性设置为Message2。
9.当Visual Studio切换到代码视图后,在Message2事件处理程序中输入下面的代码:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
10.拖拽一个SynchronizationScope活动放到右边的Sequence活动中。
11.为了让这个SynchronizationScope活动和你在第4步中插入的SynchronizationScope活动进行同步,在当前这个SynchronizationScope活动的SynchronizationHandles属性中输入SyncLock。
12.现在拖拽一个Code活动放到你刚才插入的这个SynchronizationScope活动中。它的名称命名为msg3,它的ExecuteCode属性设置为Message3。
13.在Message3事件处理程序中插入下面的代码:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
14.拖拽第四个Code活动,把它放入右边Sequence活动中的SynchronizationScope活动中。它的名称命名为msg4,它的ExecuteCode属性设置为Message4。
15.在Message4事件处理程序中插入下面的代码:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
16.工作流现在就完成了。从SynchronizedHelloWorld应用程序中添加对该工作流的项目级引用。
17.打开SynchronizedHelloWorld项目中的Program.cs文件,找到下面一行代码:
Console.WriteLine("Waiting for workflow completion.");
18.在你找到的上面一行代码的下面,添加下面的代码来创建一个工作流实例:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
19.编译该解决方案,纠正任何编译错误。
20.执行该应用程序。你可能需要在Main方法中设置一个断点才能看到输出结果。假如输出结果中显示的这两条信息还是杂乱的,你需要确认你在两个SynchronizationScope活动(步骤4和步骤11中添加)的SynchronizationHandles属性中输入的是完全相同的字符串。
我们在本章中将介绍的最后一个活动和你看过或将看到的任何其它活动都大不一样,它叫做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的活动,它来自于你即将创建的一个示例应用程序。
图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展示了这个空储备箱。
图11-2 储备箱为空时的TankMonitor用户界面
图11-3展示的是储备箱为半满时的情形。而图11-4展示出了储备箱中流体过量时的情形。和你看到的一样,储备箱下面的label控件会为你提供任何状态提示信息。这个label控件的提示信息完全由工作流的反馈结果控制,而不是被储备箱的滑动条控件直接进行控制。
图11-3 储备箱为半满时的TankMonitor用户界面
图11-4 储备箱中流体过量时的TankMonitor用户界面
在你的工作流中使用ConditionedActivityGroup活动
1.在下载的本章源代码文件夹目录中打开名称为TankMonitor的练习项目解决方案。
2.在Visual Studio打开了该解决方案后,在工作流视图设计器中打开Workflow1.cs文件。
3.拖拽一个ConditionedActivityGroup活动到工作流视图设计器界面上。
4.下图是conditionedActivityGroup1在Visual Studio的属性窗口中呈现的界面。选择UntilCondition属性,这会显示出一个向下的箭头。点击这个向下的箭头,这会显示出列表选择项,此处选择代码条件。
5.通过点击加号(+)展开UntilCondition属性,在Condition属性中输入CheckContinue。在Visual Studio添加了对应的事件处理程序后,重新回到工作流视图设计器上来。
6.生成该解决方案(按下F6),这将使本项目中的这个自定义活动呈现在工具箱中。现在,需向CAG中添加第一个子活动。从Visual Studio工具箱中,拖拽一个自定义SetLevel活动到工作流视图设计器界面上,把它放到CAG活动的矩形区域中,矩形区域的具体位置在“<”按钮的右边。如下图所示:
备注:在CAG的主窗口内显示的锁状图标表示子活动处于预览模式。(CAG主窗口上方的文字也指明了当前所处的模式。)进入编辑模式后你能对setLevel1的属性进行编辑,通过点击“<”按钮右方矩形窗口中的setLevel1活动的图标,你也能进入编辑模式并对它的属性进行编辑。
7.我们现在就先进入CAG的编辑模式,点击“预览”文字旁边的微型正方形按钮。这是一个开关型的按钮,如再点击一次这个按钮,将使CAG再次进入预览模式。
8.进入CAG的编辑模式后,你就可通过在CAG的主窗口中选中它的子活动,然后就可对它的属性进行设置。我们现在点击setLevel1活动以便激活它的属性。
9.选中setLevel1的WhenCondition属性,它将显示一个向下的箭头,然后从它的列表选择项中选择代码条件。
10.展开这个WhenCondition属性,在Condition属性的文本框中输入AlwaysExecute。Visual Studio也会自动为你添加一个对应的方法并会切换到代码视图下。我们需回到工作流视图设计器界面下,因为你还需对setLevel1活动的多个属性进行设置。
11.在setLevel1的Invoked属性中输入OnSetLevel并按下回车键。在Visual Studio添加了对应的OnSetLevel事件处理程序后,重新回到工作流视图设计器界面下。
12.你需要为setLevel1设置的最后一个属性是Level属性。选择它的Level属性,这将显示一个浏览(...)按钮,然后点击这个浏览按钮。这将打开一个“将‘Level’绑定到活动的属性”对话框。然后点击绑定到新成员选项卡,点击创建属性,在新成员名称中输入TankLevel,最后点击确定。
13.你现在需要向CAG中放入另外一个子活动。从工具箱中拖拽一个自定义Stop活动到“<”按钮右边的矩形窗口中,并把它放到setLevel1活动的右边。
14.选择stop1的WhenCondition属性,这将显示一个向下的箭头,从它的列表选择项中选择代码条件。
15.点击WhenCondition旁边的“+”,这将显示出Condition属性。在这个例子中,你可和setLevel1活动共享一个Condition属性值,因此,选中这个Condition属性,这将显示一个下拉列表框,然后从它显示出的列表项中选择AlwaysExecute。
16.接下来选中stop1的Invoked属性,在它的文本框中输入OnStop。同样,这也会添加一个OnStop方法。在添加了这个方法后重新回到工作流视图设计器界面上来。
17.现在准备向CAG中添加第三个活动。这次,拖拽一个自定义UnderfillAlert活动到设计器界面上,把它放到CAG中stop1活动的右边。
18.点击underfillAlert1活动的WhenCondition属性,从显示出的下拉列表框中选择代码条件。
19.点击“+”展开该WhenConditon属性,在Condition中输入CheckEmpty。同样,在添加了对应的CheckEmpty方法后回到工作流视图设计器界面上来。
20.接下来,你需要把underfillAlert1的level属性绑定到你在第12步中创建的TankLevel属性。为此,点击level属性激活它的浏览(...)按钮。然后点击该浏览按钮,这就打开一个“将‘level’绑定到活动的属性”对话框。但这次,你将绑定到一个现有的属性,因此只需从现有的属性中选中TankLevel即可,然后点击确定。
21.underfillAlert1活动就完全配置好了,我们现在要把最后一个活动添加到本示例应用程序的CAG中。拖拽一个自定义OverfillRelease活动到设计器界面上,把它放到CAG中其它现有活动的最右边。
22.同样,你需要设置它的WhenCondition属性,因此点击overfillRelease1的WhenCondition属性并从它的下拉列表中选择代码条件。
23.展开WhenCondition旁边的“+”,这就显示出它的Condition属性。在该属性中输入CheckOverfill。在添加了对应的CheckOverfill方法后重新回到工作流视图设计器界面上来。
24.和第20步中你为underfillAlert所做的工作一样,现在把overfillRelease1的level属性绑定到TankLevel属性上。点击level属性,这就显示出一个浏览(...)按钮。点击该浏览按钮,这将打开一个“将‘level’绑定到活动的属性”对话框。从现有属性列表中选择TankLevel,然后点击确定。
25.本工作流在视图设计器界面上的设计工作就完成了,现在需要切换到Workflow1.cs的代码视图下添加一些代码。
26.打开Workflow1.cs源文件,在源文件中找到Workflow1构造器。在构造器下面添加如下的代码,主要作用是创建在启动工作流时需要用到的储备箱中容纳流体的下限值和上限值的属性。
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/OutliningIndicators/ContractedSubBlock.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/OutliningIndicators/ContractedSubBlock.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/ExpandedBlockEnd.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/OutliningIndicators/ContractedSubBlock.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/OutliningIndicators/ContractedSubBlock.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/ExpandedBlockEnd.gif)
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方法,并为它添加下面两行代码:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
31.接下来定位到underfillAlert1的WhenConditon属性对应的CheckEmpty方法。尽管你想让CAG每次都对它的子活动的WhenCondition进行判断,但你并不想让通知(低于下限值或超过上限值时)不停地发送到用户界面上,因为这将消耗过多的CPU周期。实际上,你只想让通知在储备箱的容量状态级别发生更改后只发出一次。下面的代码就为你做这个工作,这些代码添加到CheckEmpty方法中。
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedBlockEnd.gif)
32.overfillRelease1也需要对它的WhenCondition进行判断,因此找到CheckOverfill方法并添加和上面相似的代码:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedBlockEnd.gif)
33.保存所有打开的文件,编译该解决方案。
34.执行该应用程序。向上或向下拉动滑动条可对储备箱进行补充或抽空进行模拟。当储备箱中的流体超过或低于边界条件时注意观察它下面所显示的文本。
备注:我在创建这个TankMonitor应用程序的过程中,当添加事件处理程序时会感到它非常像是一个基于状态机的工作流。假如在你创建顺序工作流的过程中发现,在一个特别的业务流程中使用一点点基于状态机的处理流程会更有用的话,CAG或许就是一个既快速又简便的极好的选择,这不用重新创建并调用一个独立的基于状态机的工作流。
信不信由你,到现在你已经看到过足够多的用来解决许多和工作流任务相关的处理过程,但这些章节讨论的话题还只是加深你对WF的理解。
本章源代码:里面包含本章的练习项目和完整代码