您的位置:首页 > 其它

【翻译】WF从入门到精通(第八章):调用外部方法及工作流

2008-05-22 14:18 357 查看
上一篇:【翻译】WF从入门到精通(第七章):基本活动的操作例

学习完本章,你将掌握:
1.创建并调用你的工作流外部的本地数据服务
2.理解怎样使用接口来为宿主进程和你的工作流之间进行通信。
3.使用设计的外部方法在你的工作流和宿主应用程序之间传输数据。
4.在一个正执行的工作流中调用其它工作流

在写前面的章节时,我自己不断地思考,“我不能再等了,我要弄清楚在哪里可把(工作流中的)真实数据返回到宿主应用程序中!”为什么?做了这么多的活动和工作流的演示,但都没有实际返回某些感兴趣的东西给宿主应用程序。我不知写过多少我们感兴趣的工作流的实例和演示,但至多只是仅仅处理过数据的初始化(就像第一章-WF简介中你看过的邮政编码的例子)。但事情变得更加有趣,坦率地说,当我们启动工作流,然后从外部源中寻找并处理数据、返回处理后的数据给我们的主应用程序要更加接近现实。
为什么不这样呢?公开一个对象,来从执行的工作流中传给宿主应用程序,或者从宿主应用程序传给工作流不就行了吗?其实,使用现有的串行化技术,如.NET Remoting或者XML Web服务,就可完成这些事。串行化,也叫序列化,它可把数据从原有的形式转换成合适的形式,以在不同进程甚至不同计算机之间进行传输。
为什么谈到序列化呢?因为你的工作流是在你的宿主进程中的不同线程上执行,不同线程之间传送数据,如不进行适当的序列化,将会引发灾难,具体原因超出了本书的讨论范围。其实,你的工作流能在一个持久化的状态下发送它的数据。这并没有在不同线程上,甚至它不在执行中。
但我们想在我们的工作流和正控制该工作流的宿主进程间传送数据时,使用.NET Remoting或者XML Web服务这样的技术为什么并没有认为是多余的呢?其实这绝对有必要!我们将创建local通信,本章将以此出发。我们将搭建必须的体系来满足线程数据序列化,以进行计算机之间或进程之间的数据传输。

创建ExternalDataService服务

当工作流和它的宿主进行通信时,在它发送和接收数据的时候,工作流要使用队列和消息。WF为我们做的越多,我们就可把重点更多的放到应用中特定任务的解决上。

工作流内部进程通信
对于简单的通信任务,WF使用“abstraction layer”来在工作流和宿主之间进行缓冲。抽象层像一个黑盒,你为它提供输入,它会执行一些神奇的任务,然后信息流出到另一边。但我们不用知道它是如何工作的。
在这种情形下,该黑盒就是一个知名的“local communication”服务。和WF术语中的任何一种服务一样,它也是另一种可插拔服务。区别是它不像WF中的那些已预先创建好的服务,你需要写出这个服务的一部分。为什么呢?因为你在宿主应用程序和你的工作流之间传递的数据有一定的特殊性。更进一步说,你可创建各种各样的数据传输方法,你可使用你设计的各种方法从宿主应用程序发送数据,然后在工作流中接收数据。
备注:这里有些事情你需要进行关注,那就是对象或集合的共享问题。因为宿主应用程序和工作流运行时在同一个应用程序域执行,因此引用类型的对象和集合就是通过引用而不是值进行传递。这意味着宿主应用程序和工作流实例在同一时间会访问和使用同一个对象,多线程环境下这会产生bug,出现数据并发访问错误。因此,对于可能要进行并发访问的对象或集合,你可考虑传递一个对象或集合的副本,或许这可通过实现ICloneable接口,或者考虑亲自序列化该对象或集合并传递序列化后的版本。
你可写这种local service,把它插进工作流,然后打开连接,发送数据。这些数据可以是字符串,DataSet对象,甚至可以是你设计的任何可被序列化的自定义对象。通信可以是双向的,尽管在本章我没有演示它。(这里,我仅仅是把数据从工作流中传回给宿主应用程序。)从工作流的角度来说,我们使用工具生成活动的目的是发送和接收数据。从宿主应用程序的角度来说,接收数据等同于一个事件,而发送数据就是在一个服务对象上的方法的简单调用。
备注:我们在后面几章看到更多的活动后还会重温该双向数据传输的概念。工作流活动从宿主应用程序中接收数据基于一个HandleExternalEvent活动,我们将在第10章“Event活动”中看到。我们也需要更深入地了解这些概念间的相互关系,这在第17章“宿主通信”中将进行介绍。对于当前,我们只是在工作流实例完成它的任务后,简单地返回复合数据给宿主。
我们需要做的还不仅仅是这一点,我们最终需要添加ExternalDataService服务到我们的工作流运行时中。ExternalDataService是一个可插拔的服务,它方便了工作流实例和宿主应用程序之间进行序列化数据的传输。在紧接下来的一节我们将写出的该服务的代码将做很多事(包括序列化数据的传输)。让我们来看看大体的开发过程。

设计并实现工作流内部进程通信
我们先决定将传送些什么数据。它是一个DataSet吗?是一个像整形数字或字符串之类的系统直接支持的对象吗?或者是一个由我们自己设计的自定义对象吗?无论它是什么,我们都将设计一个ExternalDataService能够绑定的接口。这个接口将包含我们设计的一些方法,这些方法能分别从工作流实例的角度上及宿主的角度上来发送数据和接收数据。使用该接口中的方法,我们就可来回传送数据。
我们然后需要写一些代码:外部数据服务的一部分。它表述了连接或者称作桥接代码,宿主和工作流将使用它来和WF提供的ExternalDataService进行交互。假如我们正涉及一个XML Web服务,Visual Studio会为我们自动地创建代理代码。但对于工作流来说没有这样的工具,因此我们需要亲自设计这个桥接代码。我们这里使用的“桥”实际上由两个类组成:一个connector类和一个service类。你可用你喜欢的名称来命名它们,但我推荐使用这样的名字来命名它们。connector类管理数据管道(状态维护),而service类被宿主和工作流用来直接进行数据交换。
在创建好接口后,我们将使用一个工具:wca.exe,它的位置通常是在你的“Program Files\Microsoft SDKs\Windows\v6.0A\Bin”目录下。该工具叫做Workflow communications Activity generator utility,该工具的作用是,给出一个接口,它将生成两个活动,你能使用它们去把该接口和你的工作流实例进行绑定。一个用来发送数据,为invoker,另一个用来接收数据,为sink。一旦它们创建好后,你就能从Visual Studio工具箱中把它们拖拽到工作流视图设计器上,它们也和任何其它工作流活动一样进行工作。但前面我已经提到过,我们没有一个工具创建连接桥代码,这样的工具在工作流方面一定很有用。
提示:从项目的角度考虑,我倾向于为宿主应用程序创建一个或一组项目,为前面提到的接口和连接桥创建另一个项目,为工作流代码再单独创建一个项目。这可让我方便地从宿主应用程序和工作流中添加对该接口和桥接类的引用,做到了在程序集之间进行简洁的功能隔离。
我们有了这些程序集后,我们需要连通我们的工作流和宿主应用程序之间的通信。在执行时,通过使用ExternalDataService整个过程被简化了。我们先快速看看本章中的最基本的应用程序实例(就它而言,它比我们目前看到过的例子都有复杂),然后开使创建我们需要的工作流外部数据通信代码。

机动车数据检查应用程序

本示例应用程序是一个Windows Forms应用程序,它提供了一个用户界面,上面集中了指定驾驶员的机动车数据。该应用程序本身已是很有意义的,我不想再重复创建它的每一个细节。相反,你将使用这个已经提供好了的样本代码来作为本章的起点。但是,我将展示怎样把它们绑进工作流组件中。
主用户界面窗体见图8-1。下拉列表框控件包含了三个驾驶员的姓名,选择其中一个的姓名都会生成一个新的设计好的工作流的实例来对该驾驶员的机动车信息进行检索,并返回一个完整的DataSet。该DataSet然后被绑定到两个ListView控件,一个是违规信息。
public interface IMVDataService
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;

namespace MVDataService
[Serializable]
public class MVDataAvailableArgs : ExternalDataEventArgs
public MVDataAvailableArgs(Guid instanceId)
: base(instanceId)
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;

namespace MVDataService
public sealed class MVDataConnector : IMVDataService
public static WorkflowMVDataService MVDataService
public DataSet MVData
public void MVDataUpdate(DataSet mvData)
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;

namespace MVDataService
public class WorkflowMVDataService
public Guid InstanceID
public static WorkflowMVDataService CreateDataService(Guid instanceID, WorkflowRuntime workflowRuntime)
public static WorkflowMVDataService GetRegisteredWorkflowDataService(Guid instanceID)
private WorkflowMVDataService(Guid instanceID)

~WorkflowMVDataService()
public DataSet Read()
public void RaiseMVDataUpdateEvent()
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;

namespace MVDataService
// Assign the DataSet we just created as the host data
mvDataUpdate1.mvData = ds;
指定了我们所返回的DataSet后,我们就完成了工作流的开发,并且使用了工具来把该DataSet传给宿主应用程序。但我们的宿主应用程序需要做些什么才能接收到该数据了?

在宿主应用程序中检索工作流数据

现在让我们返回到我们的主应用程序中。我们现在要做的是修改应用程序,以使用我们在本章的“创建外部数据访问”这一节中创建的桥接类。
备注:尽管这是一个简化的例子,但这个应用程序仍然是一个完全意义上的Windows Form应用程序,它演示了怎样处理工作流及其怎样进行多线程操作(比如updating控制的时候)。
为了让我们的接口可使用工作流返回的数据集,我们需要使用桥接代码中的connector类来对我们的工作流实例进行注册(为了使我们能正确的接收DataSet)。我们也需要勾住(hook)MVDataUpdate事件,以便我们的应用程序知道接收数据的时间。为方便做这些事。我们将为“Retrieve MV Data”按钮的event handler添加一点代码,并为MVDataUpdate添加一个新的event handler。
备注:假如你不熟悉匿名方法(anonymous methods)的话,现在就是简要学习它的一个好机会!

为我们的宿主应用程序添加工作流外部数据服务
1.在Visual Studio解决方案资源管理器中打开Form1.cs文件,并切换到代码视图界面。
2.找到cmdRetrieve_Click方法。在该响应按钮点击的事件方法中已经存在了初始化工作流实例的代码,但我们还需要在创建工作流实例和启动该实例之间的地方插入一些代码,也就是在调用“_workflowRuntime.CreateWorkflow”的下面添加如下的代码(为让Visual Studio为我们自动生成事件处理程序的代码,请尽量不要使用复制粘贴的方式,应在=号后使用连续两个Tab键):

// Hook returned data event.
MVDataService.WorkflowMVDataService dataService =
MVDataService.WorkflowMVDataService.CreateDataService(
_workflowInstance.InstanceId,
_workflowRuntime);

dataService.MVDataUpdate +=
new EventHandler<MVDataService.MVDataAvailableArgs>(
dataService_MVDataUpdate);
3.在Form1类中,为Visual Studio刚刚创建的dataService_MVDataUpdate事件处理程序添加下面的事件处理代码,并去掉存在的“not implemented”异常。

IAsyncResult result = this.BeginInvoke(
new EventHandler(
delegate
), null, null
); // BeginInvoke
this.EndInvoke(result);

// Reset for next request
WorkflowCompleted();
就这样!我们的应用程序就完成了,编译并执行该应用程序。当你点击“Retrieve MV Data”按钮时,选中的驾驶员姓名就会被传给工作流实例。当DataSet创建好后,该工作流实例就会激发MVDataUpdate事件。宿主应用程序代码会截获该事件进行数据的接收,然后把它绑定到ListView控件。
在最后一步我们需注意一个关键的地方,就是在我们调用WorkflowMVDataService的静态方法GetRegisteredWorkflowDataService来检索数据服务包含的DataSet后,我们使用数据服务的Read方法来把该DataSet拉进我们的宿主应用程序执行环境中以便我们进行数据绑定。

用InvokeWorkflow调用外部工作流

这儿要问你一个问题:假如你有一个正在执行的工作流,该工作流能执行第二个工作流吗?
答案是Yes!有这样一个活动,InvokeWorkflow活动,可用它来启动第二个工作流。我们通过一个例子来简要地看看这个活动。我们将创建一个新的控制台应用程序示例来启动一个工作流,该工作流只是向控制台输出一条信息。在输出该信息后,该工作流实例启动第二个工作流实例,被启动的工作流实例也输出一条信息,这样就生动地为我们展示了两个工作流都执行了。

调用第二个工作流
1.和前面一样,本章的源代码中包含了完整版和练习版两种版本的WorkflowInvoker应用程序。我们现在打开练习版的WorkflowInvoker解决方案。
2.在Visual Studio加载WorkflowInvoker解决方案后,在WorkflowInvoker解决方案中添加一个新的基于顺序工作流库的项目,工作流的名称命名为:Workflow1,保存该项目。
3.下一步,从工具箱中拖拽一个Code活动到工作流视图设计器界面上。在该活动的ExecuteCode属性中键入“SayHello”,然后按下回车键。
4.Visual Studio会自动切换到代码编辑界面。定位到Visual Studio刚刚添加的SayHello方法,在该方法内输入下面的代码:

// Output text to the console.
Console.WriteLine("Hello from Workflow1!");
5.我们现在需要添加第二个要执行的工作流,因此重复步骤2,但工作流的名称命名为:Workflow2。重复步骤3和4,但把信息“Hello from Workflow1!”替换为“Hello from Workflow2!”,当然工作流源文件的名称也要重命名为workflow2.cs,以避免冲突。
6.我们想在第一个工作流中调用第二个工作流,但这样做,我们还需要添加对第二个工作流的引用。在这之前,我们需要编译并生成Workflow1。
7.回到Visual Studio解决方案资源管理器,为Workflow1项目添加对项目Workflow2的项目级引用。
8.回到Workflow1的工作流视图设计器界面上。这次,拖拽一个InvokeWorkflow活动到你的顺序工作流视图设计器界面上。
// Create the workflow instance.
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(Workflow1.Workflow1));

// Start the workflow instance.
instance.Start();
15.我们现在将为宿主应用程序添加少量的代码,以便每个工作流完成后通知我们。在WorkflowCompleted的事件处理程序中插入下面的代码:

if (e.WorkflowDefinition is Workflow1.Workflow1)
Console.WriteLine("Workflow 1 completed.");
else
Console.WriteLine("Workflow 2 completed.");

waitHandle.Set();
第一个完成的工作流设置AutoResetEvent,以便强制应用程序等待工作流完成。我们可添加代码以使应用程序等待所有的工作流,但出于展示的目的这已足够。假如你编译并执行该WorkflowInvoker应用程序,你将在控制台中看到下面图8-4中所展示的输出结果。假如输出信息的顺序有些许的不同,不用吃惊,这是多线程程序的特征。



图8-4 WorkflowInvoker应用程序的控制台输出

本章源代码:里面包含本章的练习项目和完整代码

下一篇:【翻译】WF从入门到精通(第九章):逻辑活动
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: