WF 从入门到精通(第十章):事件活动

学习完本章,你将掌握:

1.使用HandleExtenalEvent 活动创建特定的事件处理程序

2.在你的工作流中使用Delay 活动

3.在你的工作流中使用EventDriven 活动

4.在你的工作流中使用Listen 活动

5.理解EventHandlingScope 活动在活动并发执行的情况下是怎样监听事件的


在第八章(“调用外部方法和工作流”)中,你看过工作流怎样使用CallExternalMethod

活动来和宿主应用程序进行通信。当工作流调用一个外部方法时,使用一个你提供的本地通

信服务,该宿主应用程序会收到一个事件,然后宿主对数据进行处理并产生一些相应的动作。

相 反的调用过程是宿主应用程序触发的事件被工作流捕获进行处理(尽管工作流事件处

理可被用在更广泛的任务中,而不仅仅是和宿主进行通信)。在第八章中,我提 到过在对工

作流用来处理事件的活动进行叙述后,我们还将重温宿主/工作流之间的通信,在本章中我们

将完成这件事。

在目前为止的其它章节 中,我都是单独地对某个工作流活动进行描述,然后提供一个小

应用程序来演示该活动的操作过程。和这些章节不同,本章将在一个示例应用程序中对多个

活动进行 描述和演示。为什么这样做呢?因为我在这里要描述的这些活动都是相互关联互相

依赖的。我不能演示其中一个活动而对其它的活动不进行演示。Listen 活动可作为

EventDriven 活动的容器。在EventDriven 活动的内部,你还会不出所料找到唯一的一个

HandleExternalEvent 活动等等。因此在本章中我将从始至终只创建一个应用程序来对这些

活动进行描述和演示。“宿主到工作流”这一节是本章的主线。我们首先从

HandleExternalEvent 活动开始。

使用HandleExternalEvent活动

不管在你的工作流中在何处处理事件,也不管你的工作流正处于执行状态时所发现要执

行的是什么样的活动组合,只要当一个事件来到了你的工作流路径当中,

HandleExternalEvent 活动就是最终去处理该事件的工作流活动。对我来说,.NET 的强大的

功能特性很多,它的触发和处理事件的能力就是这些最强大的功能中的一个。包括工作流事

件的处理也同样强大。

HandleExternalEvent 活动的作用是响应一个基于IEventActivity 接口的事件,它有三

个主要的成员:QueueName 属性、Subscribe 和Unsubscribe 方法。QueueName 表示正等待该

事件的工作流队列,而Subscribe 和Unsubscribe 方法用来把你的事件处理程序将要接收(或

者不将进行接收)的特定事件实例告知工作流运行时。

HandleExternalEvent 活动本身也可和CallExternalMethod 活动一起使用(我们在第8

章中看到过)。工作流使用CallExternalMethod 活动来把数据发送给宿主应用程序,但是在

工作流执行时,工作流使用HandleExternalEvent 来接收从宿主中发送过来的数据。

备注: 牢记:使用外部数据交换的时机并不仅仅是在把数据从你的宿主应用程序发送到

工作流的时候。当你创建你的工作流实例的时候,你可总是为其提供初始数据。但 是,一旦

工作流正在执行时,对于直接和你的宿主应用程序进行本地通信来说,它是唯一可使用的机

制(当然也可使用更加间接的方式替代,例如使用FTP 协议或 者Web 服务调用这些手段)。

表10-1 和表10-2 列出了使用HandleExternalEvent 活动时经常用到的一些主要的属性

和方法。注意有些方法和属性是所有活动共有的(如在第四章“活动和工作流类型介绍”中

表4-1 和表4-2 展示的一样)。我在此展示的属性和方法无疑不是所有可使用的属性和方法,

但他们却是经常要被用到的。

表10-1 经常用到的HandleExternalEvent 活动的属性

属性 功能

CorrelationToken

获取或设置一个到关联标记(correlation token)的绑定。我们将在第

17 章(“关联和本地宿主通信”)中处理关联。

EventName

活动将要处理的事件。注意如果没有对其进行设置,该活动将不会对事件

进行监听并且和宿主通信也就不可能进行。奇怪的是,忽略该属性值你不

会收到任何错误验证信息。

InterfaceType

获取或设置进行通信所要使用的接口类型。该接口必须使用

ExternalDataExchange 特性进行装饰(标记)。(你或许可回忆一下第8

章,你为CallExternalMethod 方法提供了一个相同的接口。)

表10-2 经常用到的HandleExternalEvent 活动的方法

方法 功能

OnInvoked

这是一个有很用的保护型(protected)方法,它用来把本事件参数中的值和你工

作流中的字段或依赖属性进行绑定。 重写该方法(或者处理它所触发的事件)是

检索来自于宿主并被保存到事件参数中的数据一个主要的机制,通常,你会创建

一个自定义的事件参数来把数据嵌入进参 数对象自身中。

尽管你能直接从Visual Studio 的工具箱中使用HandleExternalEvent 活动,但更普遍

的情形是使用你在第8 章中看过的wca.exe 工具来为你正使用的通信接口创建一个派生自

HandleExternalEvent 的自定义类。例如,假如在你的接口中定义了一个名称为

SendDataToHost 的事件,wca.exe 将会生成一个称作SendDataToHost 的新活动(它派生自

HandleExternalEvent),并为你指定了EventName 和InterfaceType,而且通过你创建的事

件参数也为你和SendDataToHost 事件进行了数据绑定。在本章晚些时候我将提供一个例子。

使用HandleExternalEvent 很容易,只需简单地在你的工作流中放入该活动,指定接口

和事件名。假如你需要的话,还可为Invoked 事件提供一个event handler,然后就可执行

你的工作流了。假如你使用wca.exe,就可为你提供一个派生自HandleExternalEvent 的活

动,你可直接把它拖拽到你的工作流中,在属性窗口中添加绑定,把事件参数中的数据和一

个局部字段或者依赖属性绑定在一起。

在你的工作流中有了HandleExternalEvent 活动后,在等待事件发生时所有通过该顺序

流的处理过程都会停止。在一定程度上,在你的工作流中放入这个活动的行为就像.NET

Framework 编程术语中的AutoResetEvent。和AutoResetEvent 不同的是,该处理过程的线程

不是暂停。它就像是一扇门或通道,只有在该事件被触发时才允许工作流处理过程沿着它的

路径次序继续前进。

使用Delay活动

在本书中我们目前为止已经几次看到并使用过Delay 活动,但现在我将对它进行更加正

式的叙述。为什么呢?很巧,Delay 活动实现了IEventActivity 接口,因此,它同样也被归

类为Windows Workflow Foundation(WF)的基于事件的活动。

传给Delay 的是一个TimeSpan 对象,它将延时指定的时间间隔。在延时时间过期后,它

将触发一个事件。你可通过在Visual Studio 的工作流视图设计器上,或者以编程的方式设

置一个属性(TimeoutDuration)来初始化该延时的时间间隔。它也为你提供了一个event

handler(InitializeTimeoutDuration),当Delay 活动被初始化并获取所需的时间间隔信

息时将调用该事件处理程序。

提示:延时事件和定时器(timer)事件是密切相关的。WF 没有timer 活动,但你能通

过用While 活动组合该Delay 活动来创建一个timer,本章的示例应用程序就使用了这种方

式。

HandleExternalEvent 和Delay 相对于组合(composite)活动而言,它们都是basic(基

本)活动。也就是说,HandleExternalEvent 和Delay 都只执行一个单一的功能,它们不能

作为其它活动的容器。正如你可能预料到的,这些活动的普遍用法是基于一个单一的事件来

触发一系列的活动。你又如何支配这些事件序列会是怎么样的呢?答案是使用另一个WF 活

动:EventDriven 活动。

使用EventDriven活动

EventDriven 的行为就像是一个组合活动,这个组合活动以顺序执行的方式执行它所包

含的一系列活动。这并不是说你不能在这个容器中插入一个Parallel(并行)活动,但在并

行活动之前插入的活动和之后插入的活动都将依顺序进行执行。对于它容纳的活动的唯一限

制是在执行路径上的第一个活动必须是对IEventActivity 进行了处理的活动。

(HandleExternalEvent 和Delay 就是这种类型的两个活动。)除了从基类继承而来的属性

和方法外,该EventDriven 再没有其它可使用的属性和方法。(它仅仅是一个容器。)

和顺序活动不同的是,EventDriven 在有事件触发并被第一个活动处理前是不会允许所

容纳的活动执行的。(记住,第一个活动必须处理IEventActivity。)。

EventDriven 的使用还有第二个限制。它的父活动必须是Listen、State 或者

StateMachineWorkflow 之中的一个,在有些地方你是不能把EventDriven 拖到你的工作流中

的,它只能拖到上述三种容器中。我们将在第14 章(“基于状态的工作流”)中全面介绍

State 和StateMachineWorkflow 活动。但现在还是来看看Listen 活动吧。

使用Listen活动

假如说EventDriven 的行为像是一个顺序活动的话,那Listen 活动的行为就像是一个并

行(parallel)活动。Listen 可作为两个或更多的EventDriven 活动的容器。其中的这些

EventDriven 活动选定的路径完全取决于它们中谁第一个收到事件。但是,一旦其中的一个

对某个事件进行了处理,其它的和它并行的EventDriven 活动的执行路径都会被忽略而不会

被执行,它们不会再继续等待它们各自的事件,在EventDriven 活动处理了相应的事件后,

又将按顺序继续执行接下来的路径。在它的Activity 基类所暴露出的属性和方法外,再没有

我们感兴趣的属性和方法。

需注意的是在Listen 活动内必须至少包含两个及以上的EventDriven 活动对象,并且仅

仅只有EventDriven 类型的活动能直接放到Listen 活动中。此外,Listen 不能用到基于状

态机的工作流中。为什么这里有这些规则和限制呢?

假如WF 允许少于两个的EventDriven 子活动的话,Listen 活动的作用就值得怀疑。你

更好的做法是直接使用一个EventDriven 活动。假如子活动中没有EventDriven 活动的话,

你也就没有要去处理的事件。

在基于状态机的工作流中禁止使用Listen 看 起来或许是一个很奇怪的限制,其实这是

出于可能产生循环的考虑。状态机中循环这一术语指的是一系列事件的触发彼此相互依赖。

在一定程度上,这和多线程编程 中的死锁概念相似。假如事件A 依赖于事件B 触发,但事件

B 又在等待事件A 触发才能执行,我们就说产生了循环。在基于状态机的工作流中禁用并行

事件处理是 WF 设计器用来减少产生潜在的这种循环的一种措施。

使用EventHandlingScope活动

回顾目前为止我们看到过的活动中,有处理事件的基本活动、触发事件的delay 活动、

能够组合顺序流的组合活动和组合并行流的组合活动。你相信会有结合了顺序化和并行化行

为特点的和事件有关的活动吗?这就是EventHandlingScope 活动。

EventHandlingScope 是一个组合活动,它的作用是去容纳一组EventHandler 活动(它

本身就是IEventActivity 类型的对象的容器),以及唯一一个其它的非基于事件的如

Sequence 或Parallel 之类的组合活动。非基于事件的组合活动在EventHandler 活动中所容

纳的全部事件都已处理完毕前会一直执行。在所有这些事件都已触发并被处理完后,在该工

作流的EventHandlingScope 活动外面的下一个活动才继续执行。

宿主到工作流的通信

在介绍了WF 中涉及事件的这些活动后,我现在要展示前面未完成的工作流和宿主之间的

通信体系的另一半。你可以回忆一下第8 章,我们通过在工作流实例中使用

CallExternalMethod 活 动来把信息发送到宿主进程中。这个被调用的“external method”

其实是一个你所提供的方法,它由一个你所写的本地通信服务暴露出来。该服务能把预定的

数据传给宿主并触发一个事件,这个事件发送一个数据到 达的信号,然后宿主采取措施把数

据从该服务中读出(从工作流中接收到了数据后,该服务对数据进行了缓存)。

对于相反的过程,即宿主把数据 发送给一个已经执行的工作流来说,也涉及到本地通信

服务、事件以及为处理这些事件的事件处理程序。当你为宿主和工作流之间设计好了进行通

信所要使用的接口 时(就像是第8 章中“创建服务接口”这一节所展示的一样),你在接口

中添加的方法就是被工作流用来把数据发送到宿主所使用的方法。在该接口中添加事件能使

宿主把数据发送给已经开始执行的工作流。

本章的示例应用程序将会用到我所描述过的每一个活动。一个EventHandlingScope 活动

将处理“stop processing(停止处理)”事件。一个Sequence 活动将包含一个对股票行情

更新进行模拟的工作流处理过程。当股价被更新时,新价将会被传到宿主中并在用户界面上

显示出来(如图10-1 所示)。本eBroker 应用程序并不是真实地对每一只股票代码的当前股

价进行检查,它使用一个简单的蒙特卡罗模拟法来计算最新的股价。蒙特卡罗模拟是使用了

随机数字的模拟方法,它和通过掷骰子来获取相应结果的过程类似。我们这样做的目的只是

为了去看看工作流和宿主之间是怎样进行通信的。

image.png

图10-1 eBroker 的主用户界面

该eBroker 应用程序应能让工作流知道,添加的新的当前并未被监视的股票应该要被监

视到,而添加时如该股票本已存在则不予考虑(目的是为了简化处理)。你可使用Add 和Remove

按钮来模拟股票的添加和删除。点击Add 将弹出如图10-2 所示的对话框。当你输完后点击

OK,这个新的要被监视的股票就被添加进被监视股票的列表中了。

image.png

图10-2 添加一个新的要被监视的股票

在“Ticker values”列表中选择一条记录,这会激活Remove 按钮。点击该Remove 按钮

就可把该项从被监视的股票列表中移除。该移除动作产生的结果如图10-3。你正监视的股票

被保存在应用程序的Settings 文件(XML 格式的配置文件)中。下一次你执行eBroker 时,

它将“记起”你的这些股票并重新开始进行监视。

image.png

图10-3 移除一个已存在的被监视的股票

在图10-2 中,你看到了应用程序需要知道你当前有多少股份以便能计算你所拥有的股份

的总价值,这些数字可被用来计算当前的市值。假如你后来想要修正股份的数量(通过买卖

股票),你可选中市值(market value)列表中的股票然后点击Buy!或者Sell!该对话框

如图10-4 所示。

image.png

图10-4 需要去买或卖的股份数对话框

图10-2 中的这个Add 添 加对话框也需要输入买或卖的“触发”条件值,当你不应该买

进或卖出你目前所监视的任何公司的股票时,工作流中包含的使用了这些值的业务逻辑会通

告你。假如 股票价格超过了预定的触发卖价的值,则在市值列表中将显示一个红色的标记。

假如股票价格低于预定的触发买价的值,将显示一个绿色的标记。你能在任何时候进 行买

卖...这些标记只是起提示作用,在图10-5 中你可看到这组标记。

image.png

图10-5 指出了买卖建议的eBroker 用户界面

这四个按钮(Add、Remove、Buy!和Sell!)中的每一个都会触发一个到工作流的事件。

还有第5 个事件,就是Stop,它用来停止模拟的执行过程,这个事件由Quit 按钮触发。

该应用程序的许多地方其实我已经为你写完了,这使你能把注意力放到和工作流相关的

地方。首先,你要完成工作流和宿主将用来进行通信的接口,然后你要使用wca.exe 工具来

创建继承自CallExternalMethod 和HandleExternalEvent 的一组活动。准备好了这些,你就

可使用本章中看到过的每一个活动来布置该工作流。你将看到本地通信服务是怎样把宿主应

用程序和工作流通信处理进程粘合到一起的。最后,你将简要地进行检查并添加一些代码到

eBroker 用户界面源文件中,以指引它和工作流进行交互。我们就开始吧!

创建通信接口

我们只需要一个方法:MarketUpdate,它把市场价格信息返回到用户界面上,另外还需

要五个事件,这些事件分别是AddTicker、RemoveTicker、BuyStock、SellStock 和Stop,

它们用来驱动工作流。这唯一的一个方法和五个事件都要添加到一个接口中,我们将首先创

建这个接口。任何和本地通信服务相关的事情都依赖于这个接口。

创建一个工作流数据通信接口

1.下载本章源代码,从Visual Studio 中打开eBroker 应用程序解决方案。

备注:和本书中绝大部分示例应用程序一样,本eBroker 示例应用程序也有两个版本:

完整版本和非完整版本。非完整版是学习本示例应用程序的版本,你在此需要打开该版本以

便进行本示例应用程序的练习和学习。

2.在打开的本解决方案中你将找到三个项目。展开eBrokerService 项目,然后打开

IWFBroker.cs 文件准备进行修改。

3.定位到eBrokerService 名称空间,在该名称空间中添加下面的代码并保存:

IWFBroker

4.对本项目进行编译,假如存在编译错误,修正所有错误。

不要忘记ExternalDataExchange 属性。没有它你就不能使用我在这里所描述的数据传送

机制来成功地在工作流和宿主之间进行信息的传送。

在你创建通信活动(使用wca.exe 工具)之前,花点时间来看看eBrokerService 项目中

的event arguments。MarketUpdateEventArgs 实际上只不过是

System.Workflow.ExternalDataEventArgs 的强类型版本,StopActionEventArgs 也是。

System.Workflow.ExternalDataEventArgs 这个event argument 类不传送数据,但是,

TickerActionEventArgs 和SharesActionEventArgs 都要传送数据给工作流。

TickerActionEventArgs 承载的是代表要添加和移除的股票的XML 数据,而

SharesActionEventArgs 承载的是作为主键的股票代码以及要买或卖的股票数目。

提 示:设计这些event argumeents 是很重要的,因为这些event arguments 把数据从

宿主传给工作流。此外,wca.exe 工具会检查这些event arguments 并创建到派生类的绑定,

使你能从event arguments 中访问到这些派生类中的数据,仿佛这些数据就是这些event

arguments 所固有的。换句话说,假如event arugment 有一个命名为OrderNumber 的属性,

则wca.exe 创建的类就会有一个命名为OrderNumber 的属性。它的值来自于事件 的事件参数,

并会为你自动指定该值。

现在我们就使用wca.exe 工具来创建通信活动

创建通信活动

1.点击“开始”菜单,然后点击“运行”按钮打开“运行”对话框。

2.输入cmd,然后点击确定。

3.使用cd 命令把起始目录定位到eBrokerService 项目生成的程序集所对应的目录下,

如cd "...\eBroker\eBrokerService\bin\Debug"。

4. 就如第8 章中做过的一样,在命令行提示符中输入下面的命令(包含有双引号):

"<%Program Files%>\Microsoft SDKs\Windows\v6.0A\bin\wca.exe" /n:eBrokerFlow

eBrokerService.dll。(注意该“<%Program Files%>”表示你的Program Files 目录的位置,

通常是“C:\Program Files”。)然后按下回车键。

5.wca.exe 会加载它在eBrokerService.dll 找到的程序集,然后扫描使用了

ExternalDataExchange 特性修饰的接口,在这个例子中这个接口是IWFBroker。被解析出的

那个方法被转换成派生自CallExternalMethod 活动的类并保存到名称为

IWFBroker.Invokes.cs 的文件中。那些事件也相似地被转换为派生自HandleExternalEvent

活动的类并被放进IWFBroker.Sinks.cs 文件中。在命令提示符的命令行中键入下面的命令来

对该“invokes”文件重命名:ren IWFBroker.Invokes.cs ExternalMethodActivities.cs。

6.通过在命令提示符的命令行中键入下面的命令来对该“sinks”文件重命名:ren

IWFBroker.Sinks.cs ExternalEventHandlers.cs。

7.使用下面的命令把当前目录下的这两个文件移到工作流项目的目录中:move

External*.cs ..\..\..\eBrokerFlow。

8.现在回到Visual Studio 中,向eBrokerFlow 工作流项目中添加这两个刚创建好的文

件。

9.编译eBrokerFlow 项目,在成功编译后,在工作流的视图设计器界面下的工具箱中将

呈现出AddTicker、ButStock 等自定义事件活动。

注意:作为提醒,假如编译了工作流解决方案后这些新活动没有在工具箱中呈现出来,

就请关闭eBroker 解决方案再重新打开它,以强制对这些自定义活动进行加载。在下一节我

们将使用它们。

创建broker工作流

1.在Visual Studio 的视图设计器中打开eBrokerFlow 项目中的Workflow1.cs 文件。

2.我们需要插入一个Code 活动,它被用来为一个Delay 活动(你稍后将会插入它)指定

预期的延迟时间,并初始化一些内部数据结构。因此,拖拽一个Code 活动到工作流视图设计

器的界面上,然后在ExecuteCode 属性中键入Initialize 并按下回车键,以便在工作流代码

中创建该Initialize 事件处理程序。然后,回到工作流的视图设计器界面上继续添加活动。


image.png

备注:延时时间值保存在Settings(配置文件)中。

3.接下来拖拽一个EventHandlingScope 到工作流视图设计器界面上。

image.png

4.记住,你需要为EventHandlingScope 提供一个事件处理程序以及一个子活动,以便在

它监听事件时执行。我们首先创建事件处理程序。为了存取这些事件处理程序,需要把鼠标

指针移到eventHandlingScop1 下面的微小矩形图标上。(这个矩形就是一个“智能标记。”)

然后这个矩形变成了一个更大、更黑并带有向下箭头的矩形。

image.png

点击这个向下的箭头,这会激活一个带有图标的四个快捷菜单:查看

EventHandlingScope、查看取消处理程序、查看错误处理程序和查看事件处理程序。

image.png

点击最下面的一个菜单项、切换到事件处理程序视图。你看到的这个用户界面和你在第

七章(“基本活动操作”)中看到的和错误处理程序相联系的用户界面很相似。

image.png

拖拽一个EventDriven 活动到工作流视图设计器界面上,把它放到这个矩形的中间(在

这里你会看到“将EventDrivenActivity 拖放至此”的文字说明)。

image.png

5.现在回到工具箱中,在eBrokerFlow 组件区域中找到Stop 活动。拖拽一个该活动到工

作流视图设计器界面上,把它放进你在前一个步骤所添加的EventDriven 活动中。假如你想

对多个事件进行监听的话,在这时你还可把它们都添加进去。在我们的例子中,这里只有Stop

事件是我们需要的。

image.png

6.你刚才就添加好了EventHandlingScope 活动将对停止执行进行监听的事件。下面,你

需要为EventHandlingScope 添加子活动,当监听到Stop 活动触发时EventHandlingScope 将

执行这个子活动。因此,我们需要通过第4 步中的第一个子步骤回到

eventHandlingScopeActivity1 的查看 EventHandlingScope 界面上,但你需要选择最上面的

菜单项,而不是最下面的一个。

image.png

7.拖拽一个While 活动到工作流视图设计器界面上,把它放到EventHandlingScope 活动

内。

image.png

8.指定它的Condition 属性为代码条件而不是声明性规则条件,指定该事件处理程序的

名称为TestContinue。一旦Visual Studio 添加了该TestContinue 事件处理程序后,需要

回到工作流视图设计器上,还有更多的活动要进行添加。

image.png

9.While 活动只能接受唯一的一个子活动,因此拖拽一个Sequence 活动到该While 活动

中。

image.png

10.在这里你需要一个Code 活动来对股票价值进行蒙特卡罗模拟,因此拖拽一个Code 活

动到视图设计器界面上,把它放进你在前一步骤所添加的Sequence 活动中。在属性窗口中把

它重命名为updateMarket。

image.png

11.指定updateMarket 这个Code 活动的ExecuteCode 属性为UpdateMarketValues。在

Visual Studio 添加了相应的事件处理程序后回到工作流视图设计器界面上来,以便继续布

置你的工作流。

12.在模拟完成后(你将添加的代码实际上就是进行模拟),你需要把这些潜在的进行了

修改的值传给宿主应用程序。为此,把鼠标指针移到工具箱上,找到你在IWFBroker 中创建

的MarketUpdate 活动,把它拖到视图设计器界面上并放到Sequence 活动中的你在前一步骤

中所添加的Code 活动的下面。

image.png

13.MarketUpdate 活动需要把一小段XML 放送给宿主,要做到这一点,它必须绑定到容

纳有此时将发送的XML 的字段属性。为此,在Visual Studio 的属性面板中选择

xmlMarketValues 属性,然后点击浏览(...)按钮,打开一个“将‘xmlMarketValues’绑

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

中输入Updates。最后点击确定。Visual Studio 就添加了Updates 这个依赖属性。

image.png

14.为了让你能处理来自于宿主的事件,拖拽一个Listen 活动到设计器界面上,把它放

进Sequence 活动中。

image.png

15.假如你回忆一下,你会记起IWFBroker 接口声明了五个事件,它们中的一个是我们已

经用过的Stop,还有四个事件要去处理。Listen 活动目前仅仅容纳了两个EventDriven 活动,

但添加更多的EventDriven 活动也很容易。你需要简单地拖拽多达三个的EventDriven 活动

进Listen 活动中。为什么要添加三个而不是正好的两个呢?因为第五个EventDriven 活动要

包含一个行为像是定时器的Delay 活动,当延时过期后,Listen 活动会结束该工作流线程。

然后While 活动对执行条件进行检测判断,而返回的这个条件总被设置为true,于是使While

活动不停地循环。在股票价值被更新并发送给宿主后,Listen 活动又对新一轮来自宿主的事

件进行监听。

image.png

16.在最右边的EventDriven 活动中,拖拽并放入一个Delay 活动,在属性面板中把它命

名为updateDelay。

image.png

17.接下来从eBrokerFlow 中拖拽一个SellStock 活动到工作流视图设计器界面上,把它

放到从右边数起的第二个EventDriven 活动中。

image.png

18.在Visual Studio 的属性面板中选择NumberOfShares 属性,点击浏览(...)按钮,

这会又一次打开一个“将‘NumberOfShares’绑定到活动的属性”的对话框。点击绑定到新

成员选项卡,然后再点击创建字段,并在新成员名称中输入_sharesToSell,最后点击确定。

Visual Studio 就添加了这个_sharesToSell 字段。

image.png

备注:我在这里选择创建_sharesToSell 依赖属性而不是字段是因为字段从来不会被

Workflow1 类的外部访问到。它提供的基于XML 格式的市场价值信息要传给宿主,因此应当

把外部访问权限暴露出来。

19.Symbol 属性也必须进行绑定。下面的步骤和上一步骤是一样的,只是字段名称要命

名为_tickerToSell。

image.png

20.为了卖出股票,要拖拽一个Code 活动放到SellStock 事件处理程序的下面。在它的

ExecuteCode 属性中输入SellStock,在插入了对应的事件处理程序后请回到工作流视图设计

器界面上来。

image.png

21.我们现在要对买股票的逻辑进行添加。拖拽一个BuyStock 事件处理活动(也来自于

eBrokerFlow)到设计器界面上,把它放到正中间的EventDriven 活动中。

image.png

22.使用第18 步的步骤,把BuyStock 活动的NumberOfShares 属性绑定到一个新的字段,

名称为_sharesToBuy。同样,使用第19 步的步骤,把它的Symbol 属性也绑定到一个新的字

段,名称为_tickerToBuy。

23.和你需要一个Code 活动去卖股票一样,你也需要一个Code 活动去买股票。重复第

12 步添加一个新的Code 活动,设置它的ExecuteCode 属性为BuyStock。


image.png

24.重复第17 步至第20 步两次,把RemoveTicker 和AddTicker 事件也添加到Listen 活

动中。RemoveTicker 活动的TickerXML 属性要绑定到一个新的名称为_tickerToRemove 的字

段,而为该RemoveTicker 事件添加的Code 活动的ExecuteCode 属性指定为RemoveTicker。

同样地,AddTicker 活动的TickerXML 属性要绑定到_tickerToAdd,和它相联系的Code 活动

的ExecuteCode 属性指定为AddTicker。完成这些后,Listen 活动的外观如下所示:

image.png

25.编译你的这个工作流,纠正任何出现的编译错误。

26.在Visual Studio 中打开Workflow1.cs 的源文件准备进行编辑。

27.Visual Studio 为你添加了大量的代码,因此你首先定位到Workflow1 的 构造器并

在该构造器下添加如下的代码。你插入的这些代码可被认为是初始化代码。当工作流启动时,

你将把一个数据字典传给该工作流,这个数据字典包含有以股 票代码(如“CONT”)作为关

键字的要监视的若干股票信息的集合。你也需要指定一个轮询间隔,它是再一次对股票市值

进行检测前工作流所要等待的时间值。

private Dictionary<string, eBrokerService.Ticker> _items =
new Dictionary<string, eBrokerService.Ticker>();
private string _tickersXML = null;
public string TickersXML
{
get { return _tickersXML; }
set { _tickersXML = value; }
}
private TimeSpan _interval = TimeSpan.FromSeconds(7);
public TimeSpan PollInterval
{
get { return _interval; }
set { _interval = value; }
}

28.下面定位到你在步骤2 中为你的第一个Code 活动添加的Initialize 事件处理程序。

插入下面的代码:

Initialize

提示:为了方便,我在这个初始化方法中对该Delay 活动的TimeoutDuration 进行了指

定。但是不要忘了,你也能使用Delay 活动的InitializeTimeoutDuration 方法来做同样的

工作。

29.找到TestContinue 事件处理程序,While 活动使用它来对是否继续进行循环进行判

断。插入下面的代码让While 活动不停循环(不用担心...实际上它最终会停止循环的!):

// Continue forever

e.Result = true;

30.下面要插入的代码块很长,它使用了蒙特卡罗模拟来对股票市场价进行更新。找到和

名称为updateMarket 的Code 活动(参见第10 步)相对应的UpdateMarketValues 事件处理

程序,插入下面的代码:

UpdateMarketValues

基本上,每一次更新循环,对于每一只股票将有20%的几率被修改。假如该股票的价格

将被修改,它有一半的几率会上升,有一半的几率会下降。 将改变的值是:有75%的几率是

当前每股价格的1%,有15%的几率是当前每股价格的2%,有7%的几率是当前每股价格的3%,

有3%的几率是当前每股价 格的4%。对于每一次循环,所有被监视的股票都会被更新,即使

它的价格没有变化。将被发送回宿主进行显示的数据是一个XML 字符串,它包含有各只的股

票代 码、当前价格、根据所买的该只股票数计算出来的总市值、趋势(上升还是下降)以及

是否有要进行买或卖的建议。买卖建议会显示出一个醒目的标志(红或绿), 你已经在图

10-5 中见过。

31.现在向外部事件处理程序中添加代码。首先定位到SellStock 事件处理程序,添加下

面的代码:

SellStock

32.找到BuyStock 事件处理程序,添加下面的代码:

BuyStock

33.接下来是RemoveTicker,找到它并插入下面的代码:

RemoveTicker

34.最后是AddTicker,插入下面的代码:

AddTicker

35.假如你现在对本工作流进行编译,不会出现编译错误。

现在,工作流完成了,我们需要回到我们关注的本地通信服务和宿主的结合上来。因为

我们已经在第8 章详细介绍过这方面的内容,因此我在这里不再整个进行重新介绍。假如你

打开本例中相关的文件,你会看到这些代码和第8 章中看过的很相似。

注 意:我在第8 章中提到过下面的内容,但它是一个重要的问题,您对这个问题的认识

应该得到加强:如果你在工作流和宿主应用程序中对对象或者对象的集合进行了 共用的话,

运行中就会有风险,这牵涉到多线程数据访问的问题,因为工作流和宿主将共享对同一对象

的引用。假如你的应用程序存在这个问题,当在工作流和宿主 之间传递它们时,你就可考虑

对这些对象进行克隆(在你的数据类中实现ICloneable 接口),或者使用序列化技术。对于

本应用程序,我选择了XML 序 列化。

但我想谈谈连接器类BrokerDataConnector 中的一些代码。IWFBroker 接口因为包含了

事件,因此和我们在第8 章中看到的示例的接口不同。因为连接器类必须实现该接口(在本

例中,BrokerDataConnector 实现了IWFBroker),因此该连接器也必须处理这些事件。但是,

事件的实现和清单10-1 中看到的一样,没有特别之处。假如你对该清单从头一直看到尾,你

将看到通常的事件实现和你或许亲自写过的事件实现非常相像。

清单10-1 BrokerDataConnector.cs 的完整代码

BrokerDataConnector.cs 的完整代码

当宿主执行上面这些“raise”方法来触发基于用户输入的各种事件时,工作流就会执行

连接器的MarketUpdate 方法。第8 章描述了该工作流用来调用MarketUpdate 方法的机制。

为了看看宿主怎样调用一个用来和工作流进行交互的事件(在事件参数中可根据需要携带相

应的数据),我们来看看下面的代码段。这些代码用来在点击Quit 按钮时退出应用程序。

cmdQuit_Click


为了触发传送数据到工作流中的这些事件,你首先需要使用工作流运行时的GetService

方法获取连接器。注意该服务需要为它指明恰当的连接器类型,这样才能去使用它的那些

“raise”方法。一旦得到该服务后,你就可简单地调用对应的“raise”方法,为它指定要

传送的必要的数据信息去生成对应的event arguments 就可以了。

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


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

作者:hackpig
来源:
www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!



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

发表评论:

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

会员中心
搜索
«    2024年3月    »
123
45678910
11121314151617
18192021222324
25262728293031
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 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