工作流编程循序渐进(7:InvokeWorkflowActivity活动)


作者  朱先忠

一、简介

      使用InvokeWorkflowActivity 活动可以从一个工作流中异步方式启动另一个工作流。 在已启动的工作流开始执行且工作流分支中的下一个活动执行之前,InvokeWorkflowActivity 活动即告完成
注意:
  • WF不支持递归工作流。如果工作流A能够启动工作流B,则工作流B既不能直接启动工作流A,也不能启动任何直接或间接调用工作流A的工作流。
  • InvokeWorkflowActivity活动要求工作流运行时使用当前附加到该运行时的计划程序服务创建新工作流。
  • 所调用的工作流将只能够接收输入参数。不支持在工作流完成之后获取输出参数,因为该活动以异步方式调用工作流。
  • 工作流之间的标准通信规则适用于与InvokeWorkflowActivny活动所创建的新工作流实例进行通信。


使用InvokeWorkflowActivity活动的步骤如下:
  •    拖动一个InvokeWorkflowActivity到工作流中希望的位置处。
  •    设置TargetWorkflow属性为希望执行的工作流的类型(Type)。
  •    为TargetWorkflow设置所需要的值。
    
      当设置TargetWorkflow属性时,该活动提供了对话框允许从所有引用到的活动类型列表中导航到正确的类型,但是只有派生自Activity的类 会被显示在列表中。为了引用一个新的工作流类型,必须首先添加到包含工作流的项目或程序集的引用。具体对话框请参考本文后面的图示。
      一旦定义了TargetWorkflow属性,工作流的参数集合属性将使用定义在TargetWorkflow中的其他任何属性所更新。允许开发人员在属性窗口中为任何所需的属性设置值,可以设置静态值或者是绑定属性到当前工作流的其他属性或者是其他活动的其他属性。
      InvokeWorkflowActivity提供了一个Invoking事件允许开发人员使用代码处理。该事件在创建一个新的工作流之前触发,这使开发人员在开始一个新的工作流之前能够有机会完成一些设置任务。
 
关于InvokeWorkflowActiv时的一个重要方面工作流将以异步的方式执行,因此不会等待新工作流的执行完成。因为执行过程是异步的,所以无法获取另一个工作流的输出参数。通常需要和宿主建立额外的通信机制来获取其输出。


二、创建控制台顺序工作流示例程序框架

       说明:本文创建的InvokeWorkflowActivityDemo示例演示了如何在一个状态机工作流内部调用另外的一个工作SubWorkflow,并且定义了本地服务接口实现,使用HandleExternalEvent活动调用外部事件以等待被调用的工作流实例执行完成。该活动需要等待一个事件的触发才能够继续工作流的运行,而在Program.cs中,设置了只有当指定非宿主工作流执行完毕后,才触发事件。因此这实现了一种等待被调用工作流执行完成才继续执行的效果。
重要提示
本实例的学习基于WWF中的许多新概念(不包括在以前的教程中),请结合后面的参考资料全面理解。个别难点,请不必过于担心,我会在后面的系列文章中作细致的剖析。

      请遵循如下步骤创建一个控制台状态机工作流示例程序:
1. 启动VS2008,单击菜单”文件“|”新建“|”项目“,创建一个名字为
InvokeWorkflowActivityDemo的控制台状态机工作流示例程序。
2.之后,系统自动打开工作流设计器界面。
3. 从工具箱中拖动四个State活动到工作流设计器中。然后,再依次把两个StateInitialization活动分别拖动到前两个State活动中,再拖动一个EventDrivenActivity活动到第三个State活动中。最后,再使用拖动手柄的方法创建四个State活动的转换关系,得到如图所示的情形。
工作流编程循序渐进(7:InvokeWorkflowActivity活动)


三、添加另一个子状态机工作流


右单击示例工程,选择“添加”-“State Machine Workflow...”(如下图所示),添加一个状态机工作流,命名为SubWF(见后面的图)。
工作流编程循序渐进(7:InvokeWorkflowActivity活动)

工作流编程循序渐进(7:InvokeWorkflowActivity活动)

工作流编程循序渐进(7:InvokeWorkflowActivity活动)

在上图子状态机设计器中仅拖入一个StateInitialization活动和一个StateFinalization活动,如下图所示。
工作流编程循序渐进(7:InvokeWorkflowActivity活动)
右单击第二个活动,选择“设置为已完成状态”选项,使之成为整个子工作流的最终状态。然后,双击第一个状态中的StateInitializationActivity1活动,创建如下图所示的简单活动流程。
工作流编程循序渐进(7:InvokeWorkflowActivity活动)
双击子活动codeActivity1,输入以下代码:

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
    Console.WriteLine("这是发自子状态机中的消息。");
}



四、使用InvokeWorkflowActivity 活动调用子状态机工作流

在宿主工作流的stateActivity1状态活动中,添加了一个InvokeWorkflowActivity(双击stateActivity1状态活动内部的stateInitializationActivity1,然后拖入一个InvokeWorkflowActivity),如下图所示。

工作流编程循序渐进(7:InvokeWorkflowActivity活动)

InvokeWorkflowActivity1的TargetWorkflow指定为当前项目中前面创建的SubWF工作流,相关设置对话框如下图所示。
工作流编程循序渐进(7:InvokeWorkflowActivity活动)



  InvokeWorkflowActivity是放置在StatelnitializationActiv时容器内部的,以便于在进入StateActivity活动时总是最先运行子工作流。

五、添加HandleExternalEventActivity活动

在宿主工作流的stateActivity2内部的EventDrivenActivitv内部放置拖入一个HandleExternalEventActivity。
注意:HandleExternalEventActivity活动用于等待本地服务中的InvokedWorkflowComplete事件触发,并阻止当前工作流的继续执行。
工作流编程循序渐进(7:InvokeWorkflowActivity活动)

对于HandleExternalEventActivity活动, 必须设置它的参数InterfaceType和EventName。方法是,单击属性窗口中参数InterfaceType右边的“...”符号,弹出一个对话框如下所示:
工作流编程循序渐进(7:InvokeWorkflowActivity活动)

从右图选定我们事先已定义好的接口,单击“确定”按钮。
然后,单击属性窗口中参数EventName右边的下拉箭头,从中选择已经在选定的接口中声明的事件(在本例中是InvokedWorkflowComplete)。

六、定义在工作流实例与宿主间通信的本地服务


WWF中的服务可分为核心服务和本地服务。核心服务由WF定义,而本地服务(也称为数据交换服务)则是开发人员自定义的。本地服务可以是任何想在WF中实现的服务,一个通常的用处是使用本地服务在工作流实例与宿主之间进行通信。有关于“本地服务”的全面讨论是一个复杂的话题,我想在本系列后面的学习教程中对之展开全面深入的探讨,在此不赘述。

[一]定义接口

根据WWF中本地服务的定义要求,首先要定义一个修饰以ExternalDataExchange属性的接口,我们对之命名为ILocalService,代码如下:

[ExternalDataExchange]
internal interface ILocalService
{
    event EventHandler<ExternalDataEventArgs> InvokedWorkflowComplete;
    void WorkComplete(Guid HostWFGuid);
}

[二]创建本地服务类

然后,基于上述接口创建一个本地服务类,代码如下:
//定义一个本地服务实现,该服务将被添加到工作流运行时引擎中
internal class LocalService : ILocalService
{
    public event EventHandler<ExternalDataEventArgs> InvokedWorkflowComplete;
    public void WorkComplete(Guid HostWFGuid)//实现接口中声明的方法
    {
        if (InvokedWorkflowComplete != null)
        {
            InvokedWorkflowComplete(null, new ExternalDataEventArgs(HostWFGuid));
        }
    }
}

上面定义中,基本遵循了“死”格式,除了方法名称外。在本文中先不详细讨论。

七、在宿主中操作工作流实例与本地服务

根据前面的要求,本地服务负责工作流实例与宿主间的通信中介,而加载本地服务的工作是在宿主中完成的。下面给出了宿主部分(program.cs)完整的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;


using System.Workflow.Activities;//ExternalDataExehangeService 

namespace InvokeWorkflowActivityDemo
{
    class Program
    {
        static Guid HostWFGuid;//用于记忆父工作流实例的ID标记
        static LocalService ls;

        static void Main(string[] args)
        {
            using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            {
                //下面这几行是必需的死套路
                //加载本地服务
                ExternalDataExchangeService dataService = new ExternalDataExchangeService();
                workflowRuntime.AddService(dataService);
                
                //将自定义的本地通信服务加载到本地服务中

                ls = new LocalService();
                dataService.AddService(ls);


                //事件初始状态为终止状态(此时任何线程可以使用此事件)
                AutoResetEvent waitHandle = new AutoResetEvent(false);
                workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
                {
                    //如果将要产生仅一个子工作流,那么需要检查工作流是否完成,而不是主工作流。
                    if (e.WorkflowInstance.InstanceId != HostWFGuid)
                    {
                        //通过本地服务的特定方法通知主工作流,调用工作流完成
                        ls.WorkComplete(HostWFGuid);
                    }
                    else
                    {
                       
//此时是主工作流自身,将事件的状态位置设置为终止状态,允许一个或多个等待线程继续。
                        waitHandle.Set();
                    }
                };

                workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
                {
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                };
                //启动父工作流实例
                WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(Workflow1));
                HostWFGuid = instance.InstanceId;//记下父工作流实例的ID标记
                instance.Start();

                waitHandle.WaitOne();//阻止当前线程,直到当前waitHandle收到信号。
                Console.ReadLine();
            }
        }
    }
}



根据MSDN声明,启动工作流的实例之前,必须将 ExternalDataExchangeService 添加到工作流运行时引擎,然后将自定义通信服务添加到 ExternalDataExchangeService。详情请参考MSDN文章《在工作流中使用本地服务

八、运行实例

按F5运行控制台程序,一般顺利的话,将得到如下图所示运行时快照。

工作流编程循序渐进(7:InvokeWorkflowActivity活动)

九、参考资料

  1. 对.net工作流理解(整理中)--暂告一段落(http://dugufeilong.javaeye.com/blog/179421)。

  2. AutoResetEvent abc(http://blog.csdn.net/BackStrokeFish/archive/2007/07/03/1676982.aspx)。

  3. MSDN文章《在工作流中使用本地服务》。