首页 > 编程开发 > Objective-C编程 >
-
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活动到设计器界面上。
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.在你找到的上述代码下,添加下面的代码以便创建工作流实例:
// 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方法内)设置一个断点。
和你在上面的图片中看到的一样,输出的信息是杂乱的。假如左边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的事件处理程序中输入下面的代码:
_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。
9.当Visual Studio切换到代码视图后,在Message2事件处理程序中输入下面的代码:
_msg = " World!n";
PrintMessage();
10.拖拽一个SynchronizationScope活动放到右边的Sequence活动中。
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。
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属性中输入的是完全相同的字符串。
我们在本章中将介绍的最后一个活动和你看过或将看到的任何其它活动都大不一样,它叫做ConditionedActivityGroup活动。它具有并行和循环两方面的特征。让我们瞧瞧吧。
使用ConditionedActivityGroup(CAG)活动
简要地说,CondtionedActivityGroup活动(通常都称作CAG)是一个组合活动,它为你提供了一个角色,使你能对要执行的并行子活动进行调度。总的来看,它会运行到你指定的一个条件为true时为止,假如你没有指定这个条件,它则会运行到所有的子活动都报告它们没有更多要去执行的任务时为止。我提到的这个条件就是CAG的until condition。
这样的结果是并行执行路径不会同时被执行,因此它们并不是真正意义上的多线程中的并行执行。但是,它们执行时就像是在并行操作,并且你也可这样看待它们。把Parallel活动看成是真正意义的并行过程是最明智的:你可像对待任何多线程下的处理过程来对待并行活动。
注意:假如你需要强制指定并行执行路径间的顺序,可考虑使用SynchronizationScope活动。本章晚些时候我将对它进行演示。
在此时有个值得关注的问题:“有Delay活动会怎么样呢?”正如你知道的,顺序工作流中的Delay活动会停止执行指定的TimeoutDuration时间间隔。那这会停止Parallel活动的处理过程吗?答案是不会。延时会导致特定的顺序工作流路径将被停止,但其它并行路径会正常地继续进行处理。
考虑到我所发出过的所有这些多线程警告,你或许会认为使用Parallel活动是一个挑战。事实上,它非常容易使用。它在工作流视图设计器中呈现出来的样式和Listen活动(该活动在第10章“事件活动”中讨论过)非常相像。和EventDriven活动不同的是,你将会在它里面找到Sequence活动,除此之外,表现出来的可视化界面都是相似的。我们这就创建一个简单的例子来演示一下Parallel活动。
创建一个带有并行执行过程的新工作流应用程序
1.为了快速地演示Parallel活动,本例子使用的是一个基于控制台的Windows应用程序。我们需要下载本章源代码,源代码中包含有练习版和完整版两个版本的解决方案项目,完整版中的项目可直接运行查看运行结果,我们在此使用Visual Studio打开练习版中的解决方案项目文件。
2.在Visual Studio打开了ParallelHelloWorld解决方案后,找到Workflow1工作流并在工作流视图设计器中打开它。