【翻译】WF从入门到精通(第三章):workflow实例
上一篇:【翻译】WF从入门到精通(第二章):workflow运行时
学习完本章,你将掌握:
1.使用不带参数和带参数二种方式初始化一个workflow实例
2.测定你运行中的workflow实例的状况
3.停止workflow实例
4.确定你的workflow空闲或终止的原因
一个workflow实例由一个或多个活动组成。(我们将在第七章开始介绍各种活动:“Basic Activity Operations.”)“primary activity”或者“root activity”被称作“workflow definition”。“workflow definition”通常的行为是为其它将要工作的活动充当一个容器。
注:“workflow definition”是你要求workflow去执行的东西,而一个workflow实例是一个正在执行的“workflow definition”。它们之间有明显的区别,一个正在执行当中,而另一个则不是。
workflow实例从哪里来?它们当然应由你来创建。如你有困难来完成这个任务,并且自动创建的workflow符合你的应用要求的话,也可由软件来完成,但至少你也要写出workflow的任务或者workflow运行时将为你执行的任务。Microsoft提供了workflow运行时,你还得创建余下的东西。毕竟,这是你的应用。
WF在上述这些地方的创建上可以为你提供帮助,WF不仅将执行你创建的workflow实例,而且也将帮助你去创建它们。WF集成了丰富的图形界面设计器,它能帮你以相同的方式把workflow集成到你创建的ASP.NET Web Forms、Windows Forms或者WPF应用中。你可在工具箱上滚动鼠标,从许多活动项中选中一个,然后把它拖到设计界面上并释放它。假如这个活动项具有可配置的属性,你还可使用Visual Studio中的属性面板来配置它,使它符合你的意图。我们已在第一章简要地使用过workflow设计器,在这里我们将再次使用它,毕竟与WF相关的工作几乎全是创建workflow任务,workflow可视化设计器的使用是开发过程中巨大的一个组成部分。
workflow实例和任何其它软件类似。它们会开始执行、运行,直到遇到终止条件时终止。这些或许是数据库中的所有记录已被处理,所有需被压缩的档案已被压缩,或者workflow发向各个审批方的文档已被批复(同意或不同意),或者是处理已经完成。它只有一个正常的启动位置,但有一个或多个正常的可能停止的位置。
实例也能维持错误、异常。你可以处理这些异常也可不处理它。在某些情况下,或许你不想去处理出现的异常,并留到以后进行处理。
有时,一个workflow处理过程会执行很长很长时间才能完成。例如,一个处理过程发送了一份零件的订单并等待订单被接收。在workflow终止前须确认零件的型号和数目,而这或许会花去几天,几周甚至几月。因此,难道一个workflow实例也需要在内存里维持激活状态几天,几周或者几月吗?假如服务器崩溃或电源断电怎么办?你的workflow实例、数据、应用程序状态不是通通丢失了吗?
workflow实例和组成实例的活动是workflow处理过程中的重要部分。WF已经为workflow实例的创建及执行提供了强大的支持。我们就来看看WorkflowInstance对象。
WorkflowInstance对象介绍
workflowInstance是一个WF对象,它为你提供了你的独立的workflow任务上下文(环境)。你可使用这个对象去找到在你的处理任务中事情将是如何进行的。就像我们有方法和属性去控制workflow运行时一样,我们也有方法和属性并用它们和我们的workflow实例进行交互。表3-1列出了大多数WorkflowInstance属性,表3-2列出了经常使用的方法。我们还将在第五章看到一些额外的属性和方法,“工作流跟踪”。
表3-1 WorkflowInstance的属性
属性 | 功能 |
InstanceId | 得到workflow实例的唯一标识(一个Guid) |
WorkflowRuntime | 得到本workflow实例的WorkflowRuntime |
方法 | 功能 |
ApplyWorkflowChanges | 通过WorkflowChanges对象申请对workflow实例进行更改。这允许你在workflow执行时修改它(增加、移出或更改活动),当动态的更改实施时,workflow实例会被暂停。 |
GetWorkflowDefinition | 检索本workflow实例的根(root)活动。 |
Resume | 恢复执行先前被暂停的workflow实例。假如workflow实例并未处于暂停状态,则不做任何事情。假如workflow实例处在暂停状态,workflow运行时就会在workflow实例刚被恢复后触发WorkflowResumed事件。 |
Start | 启动一个workflow实例的执行,在这个workflow实例根活动上调用ExecuteActivity。假如Start发生异常,它通过调用Terminate终止这个workflow实例,并附加异常相关信息作为终止的原因。 |
Suspend | 同步暂停本workflow实例。假如workflow实例本就处于暂停状态,则不做任何事情。假如workflow实例正在运行,则workflow运行时就暂停该实例,然后设置SuspendOrTerminateInfoProperty(说明原因)并进入Suspend,触发WorkflowSuspended事件。 |
Terminate | 同步终止本workflow实例。当宿主需要终止workflow实例时,workflow运行时就终止这个实例并试图持久化实例的最终状态。然后WorkflowInstance设置SuspendOrTerminateInfoProperty(说明原因)并进入Terminate。最后,它触发WorkflowTerminated事件并把终止原因传到WorkflowTerminateException中的Message属性并包含到WorkflowTerminatedEventArgs事件参数中。另外,假如在持久化时发生异常,workflow运行时取而代之地就把异常传到WorkflowTerminatedEventArgs事件参数中。 |
启动一个工作流实例
当我们启动一个workflow实例前,我们必须有一个workflow任务让WF去执行。在第一章,我们通过Visual Studio为我们创建了一个基于workflow的项目,它自动包含一个workflow任务,我们对它进行了修改以进行U.S.和加拿大邮政编码的验证。如果需要的话,我们可以返回到那个项目去复制源代码,或者引用PCodeFlow.exe程序集。然后我们就可直接使用这个已创建的workflow。实际上,你可以这么去做。
然而,我们还是应该试着去学会写workflow的应用。让我们通过使用一个包含延时的顺序工作流去模拟一个长时间运行的任务吧。我们将在延时前执行一些代码,以弹出一个信息对话框。在经过延时后,我们将再次弹出一个信息对话框来指明我们的工作已经结束。通过本书的学习过程,我们的例子将会越来越详细和丰富,但现在我们还处于入门阶段,我们还将保持我们的例子并把注意力更多的放到概念上而不是提高技巧上。
注:记住,顺序工作流执行活动时一个接着一个。这个处理方式可和状态机工作流做下比较,状态机工作流执行活动时是基于状态的转变。假如你现在对此一片茫然的话,不用担心,我们将在下章进入该主题。
在WorkflowHost解决方案中添加一个顺序工作流项目
1.启动Visual Studio 2008,加载上一章创建的名为“WorkflowHost”的解决方案准备进行编辑。
2.在解决方案中添加一个崭新的workflow项目。
3.项目模板选择顺序工作流库。
4.项目名称起名为:LongRunningWorkflow。
现在打开workflow的视图设计器准备创建我们的workflow任务。在视图设计器中的大图片中,我们将添加三个活动到这个新workflow任务中:两个Code活动和一个Delay活动。Delay活动将被放到两个Code活动中间,目的是可让我们在Delay执行前和执行后都将弹出一个信息对话框。最初我们会指定一个合适的延时时间值,但稍后我们将对workflow任务进行修改,以使workflow任务初始化时能接受我们专门指定的一个延时时间值。
创建这个模拟需执行很长时间的顺序工作流
1.激活workflow视图设计器,移动鼠标到工具箱中。
2.从工具箱中选择Code活动,并把该组件拖拽到workflow设计器的表面。
3.释放鼠标并让Code活动组件落到该顺序工作流中。
4.就像在第一章一样,我们将添加一些代码到Code活动中以使worflow任务经过这个活动时执行。在此单击Code活动以确保该活动的属性面板已被激活。
5.在属性面板中激活ExecuteCode属性的下拉编辑框,它将允许你命名将被触发的事件,该事件在Code活动中的代码执行时触发。
6.输入“PreDelayMessage”。这样就添加了一个事件到worflow代码中。稍候,我们将修改这段代码以显示一个信息对话框。但现在我们仍继续在workflow的视图设计器上工作,因为我们需要添加另外两个活动。
7.从工具箱中选择Delay活动并添加到Code活动的下面。
注:顺序活动,就像我们现在所做的工作一样,是以顺序的方式执行活动。顺序由workflow视图设计器中活动的位置决定。在workflow设计器窗口的顶部的活动首先执行,对于其它活动的执行顺序则按到视图设计器窗口底部的走向(箭头)决定。在下章我们还将重温这一过程。
8.我们需要为我们的Delay活动建立一个延时时间值。为此,我们要在Visual Studio属性面板中改变TimeoutDuration的属性。把最后两个“00”改为“10”,意思是Delay活动将等待10秒钟才允许workflow继续下一步的处理。
9.现在我们需要添加第二个Code活动来显示第二个信息对话框。为此,重复步骤2和步骤6添加一个新的Code活动,但设置ExecuteCode的属性为“PostDelayMessage"来作为事件的命名。以下是在workflow视图设计器中展示的workflow的最终结果:
我们还剩下两个任务未完成。最终,我们需要把我们的workflow程序集引入到我们的主应用程序中以便执行它。但首先,我们必须添加必要的代码以显示那两个信息对话框。我们已经在我们的workflow代码创建了两个事件:PreDelayMessage和PostDelayMessage。我们将为它们添加事件处理代码,在里面实际上就是弹出信息对话框的代码。
为延时前和延时后的事件添加代码
1.单击LongRunningWorkflow项目中的Workflow1.cs文件,查看其代码。
2.添加“System.Windows.Forms"的引用,并在Workflow1.cs文件声明以下名称空间:
using System.Windows.Forms;
3.定位到新插入的PreDelayMessage方法,在方法中插入以下代码:
MessageBox.Show("正在执行延时前的代码。");
4.和上一步类似,定位到新插入的PostDelayMessage方法,在方法中插入以下代码:
MessageBox.Show("正在执行延时后的代码。");
假如你这时编译这个解决方案,那没有任何错误,但是WorkflowHost应用程序仍旧会像先前一章一样挂起。为什么呢?因为尽管我们创建了一个我们能够使用的workflow程序集,但我们并未请求主应用程序去执行它。WorkflowCompleted事件从未被触发,因此自动重置事件也就不会释放应用程序主线程。
为执行我们的workflow任务,我们要引用我们新创建的workflow程序集并添加代码,以使WorkflowRuntime对象来揭开workflow任务工作的序幕。我们现在就开始吧。
宿主一个自定义workflow程序集并启动一个不带参数的workflow实例
1.首先在项目WorkflowHost中添加对项目LongRunningWorkflow的引用。
2.假如我们现在去编译这个应用程序,WorkflowHost将编译失败。为什么呢?原因是在前一章我们创建WorkflowHost项目时,我们仅仅添加了必须的引用以支持当时的环境编译通过。但现在,我们添加了一个“System.Workflow.Runtime”引用。同时又引入了一个现成的workflow程序集到我们的宿主应用程序中,因此我们需要为WorkflowHost项目添加更多的和workflow相关的引用。我们需要添加的引用有“System.Workflow.Activities”和“System.Workflow.ComponentModel”。
3.打开Program.cs文件并定位到Main方法内的下面代码上:
Console.WriteLine("等待workflow完成。");
4.在上述代码下面添加以下代码:
instance.Start();
执行结果如下:
让我们回到下面非常关键的代码上:
instance.Start();
启动一个带参数的workflow实例
带输入参数启动的Workflow实例把接收到的参数和相关的公有属性对应起来。就是说,为传入一个可变的延时值,我们只需在我们的workflow实例上创建一个公有的名为“Delay”属性,并在创建这个实例时提供延时值即可。假如你对XML序列化和.NET中的“XmlSerializer”熟悉的话,创建一个workflow实例的过程就和把XML流反序列化成一个.NET对象的过程相似。事实上,这几乎差不多。
期望被传入workflow实例的参数值通常存储在一个Dictionary对象的Values中,Dictionary对象的关键字使用string类型,对应的值使用简单的Object对象。典型的代码如下:
Dictionary
然后你可使用Dictionary对象的Add方法添加参数。关键字必须是一个string,它表示workflow的root活动所暴露的公有属性的名称。另外,对应的值的类型必须和活动的属性类型一致。例如,我们传入一个整形类型的延时值并且我们的workflow实例中暴露了一个名为Delay的属性和其对应,那添加一个参数到Dictionary中的代码就应像下面的一样:
parms.Add("Delay",10); //延时10秒。
我们再次来写一些代码吧,我们相对做些小的修改,但这会获得许多功能。我们以控制台命令行的方式接收我们输入的一个整形数值作为延时值。为使我们的程序不会永远运行下去,我们会把这个值限制在0到120之间,意思延时范围从0秒到两分钟之间。我们也将对workflow增加Delay属性。让我们一起来对我们的workflow组件做第一次修正。
为workflow添加一个输入属性
1.打开Workflow1.cs文件准备编辑。
2.在Workflow1的构造函数后,添加以下代码:
public Int32 Delay
{
get { return _delay; }
set
{
if (value < 0 || value > 120)
value = 10;
if (ExecutionStatus == ActivityExecutionStatus.Initialized)
{
_delay = value;
delayActivity1.TimeoutDuration = new TimeSpan(0, 0, _delay);
}
}
}
启动一个带参数的workflow实例
1.打开Progrom.cs文件准备编辑。
2.定位到下面的代码上:
workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);
string val = args.Length > 0 ? args[0] : "10";
if (!Int32.TryParse(val, out delay))
{
Console.WriteLine("你必须输入一个整形值!");
return;
}
Dictionary<string, object> parms = new Dictionary<string, object>();
确定Workflow实例的状态
有趣的是,假如你看看workflow运行时对象及workflow实例对象的方法和属性,你找不到和状态相关的属性。你怎么知道是否有一个workflow在执行呢?假如有一个,它处在那个状态呢?空闲吗?正执行当中吗?我们怎么确定?
我将向前跳一小段,这其中大部分逻辑都放在workflow状态的确定上。一个给定的workflow实例的workflow definition为您提供workflow的执行状态。基类Activity暴露了一个ExecutionStatus属性,它是一个ActivityExecutionStatus枚举的一个成员。下表3-3列出了ActivityExecutionStatus的枚举值和相关的意义。
表3-3 ActivityExecutionStatus枚举值
属性 | 功能 |
Canceling | 活动正在取消中。 |
Closed | 活动已被关闭。 |
Compensating | 活动处于补偿状态。 |
Executing | 活动当前正在运行。 |
Faulting | 活动已产生并维持一个异常。 |
Initialized | 活动已被初始化但还未运行。 |
确定workflow实例执行状态
1.打开WorkflowHost项目的Program.cs文件准备编辑。
2.找到Main方法并定位到下面的代码上:
instance.Start();
3.为了让我们看到workflow实例的状态,我们直接查询workflow definition的状态并把结果输出到控制台中显示出来。在上一步中定位到的代码下插入以下代码:
Console.WriteLine("workflow处在:{0}状态。",
instance.GetWorkflowDefiniton().ExecutionStatus.ToString());
终止Workflow实例
假如你需要这样做的话,你也能容易地终止一个workflow实例,方法是通过执行workflow实例对象的Terminate方法。假如你在你的应用中添加了WorkflowTerminated的事件处理,你就能从Exception的Message属性获取终止的原因。你将发现Exception被包装到WorkflowTerminatedEventArgs中,并传入到WorkflowTerminated的事件处理程序中。这些代码WorkflowHost中已经包含了,我们还需添加一行代码来结束workflow实例。
终止workflow实例
1.打开Program.cs文件,找到如下我们刚添加的代码上:
Console.WriteLine("workflow处在:{0}状态。",
instance.GetWorkflowDefinition().ExecutionStatus.ToString();
2.在上述代码下添加以下代码:
instance.Terminate("用户取消");
假如你现在编译并运行WorkflowHost程序,为他提供一个25秒的延时值,你不会再看到任何一个信息对话框,控制台的输出结果如下:
Dehydration和Rehydration
在我们离开workflow实例的这一话题之前,我想再谈谈“dehydrating”和“rehydrating”一个实例的概念。假如你有一个长时间运行的workflow任务或者有大量的任务执行,你就能卸载任务并把必须的执行环境信息存储到一个SQL Server数据库中,这要用到运行在WF之上的一个服务。
我们将在第六章详细讨论存储的问题,我在这提及它是因为,对一件事来说,处理的目标是workflow实例。但另一方面,我们应听听这些术语,我不想让你在深入此书后却还不理解它们的基本意思。
当你“dehydrate”一个实例时,你就正在把它从执行状态中移除并进行存储以便以后恢复。典型的做法是使用WF的持久化服务,但你也能写你自己的服务来做同样的任务。以后当你的应用程序侦测到需重启workflow实例时,你就“rehydrate”这个实例它就返回当时的执行状态。这样做的原因有很多,所有这些本书稍后都会简要说明。
源码下载:WorkflowHost