WF 从入门到精通(第六章):加载和卸载实例

学习完本章,你将掌握:

1.理解工作流实例为什么要卸载和重新加载及其时机

2.理解工作流实例为什么要持久化及其时机

3.搭建SQL Server 2005,使其为WF 和工作流持久化提供支持

4.使用SqlWorkflowPersistenceService 服务

5.在你的工作流代码中进行实例的加载和卸载

6.使持久化服务能自动地加载工作流实例及卸载空闲中的工作流实例


假如你花点时间真正考虑该怎样使用WF 和工作流在你的应用程序中进行处理的话,你或许想像的许多解决方案中都包含那些需长时间运行的处理过 程。毕竟,商业软件本质上就是模拟和执行业务处理过程,这些许多的处理过程中都包含人或厂商、订货和发货、计划安排等等。人没有在几毫秒内自动进行处理的 响应能力,但在已加载的企业服务器上则能做到点。

服务器是宝贵、繁忙的资源,需让它进行线程的轮转,让线程等待几分钟、几小时甚至几天、几周是不能接受 的,原因有很多。

因此WF 的设计器必须提供一个机制,当等待一些长期运行的任务时,空闲工作流应暂时脱机。WF 决定提供 Microsoft SQL Server 作为一个可选的存储介质,因为数据库是储存(而不是失去)宝贵数据的好地方。WF 也集成了另一个可插拔服务,我们可轻易地把它纳入我们的工作 流中以支持其持久化机制。怎么做到这些、为什么做、什么时候做是我们在本章中将探讨的问题。


持久化工作流实例


你知道现代Microsoft Windows 操作系统本质上是一个非常特别的批处理软件,它负责为各请求线程分配占用处理器的时间吗?假如一个单一的线程独占了处理器一个过份的时间周 期,其它线程会“饿死”,系统将会死锁。因此这个批处理软件,也就是任务调度程序,要把线程从处理器的执行堆栈上移进和移除,以便所有的线程都能得到执行 时间。

从某个意义上说,工作流也类似。假如你有许许多多长时间运行的工作流,它们都挂到某一特定的计算机上竞争处理时间和资源的话,那么 系统最终会阻塞未处理的工作流。这就

没有了可伸缩性。事实上,WF 在维护它的工作流队列时是非常高效的,但你可能对这些必须有一个物理上的上限表示赞同, 把空闲、长期运行的工作流从激活执行状态移除是一个好主意。

或者发生什么意外,系统忽然被关闭呢?工作流完全在内存中处理,除非我们采取 步骤持久化它们。因此,除非我们在突发事件发生之前有所准备,否则我们就将丢失了执行中的

工作流实例。假如那些长期运行的工作流正管理着关键的进程,它们 丢失了我们能承受得起吗?在大多数情况下,我们都承受不起,至少我们不会心甘情愿地允许这些进程在毫无防备措施的情况下就消失掉。

好消息 是WF 不仅为您提供了卸载工作流实例,然后又重新加载的方法,它也支持一个服务:SqlWorkflowPersistenceService,它用来把 工作流实例序列化进SQL Server 数据库。

假如你读过前面一章,你可能已经熟悉甚至对把工作流实例的信息写入数据库的主张感到满意。

因 此,在什么时侯卸载工作流实例,并且假如他们被卸载,我们应怎么持久化它们呢?

在执行中有一些非常特别的点可卸载工作流实例。在大多数情况下,发生这种情 况是在我们会自动为刚才我之所以提到的——WF 不能离开(或不愿离开)长期运行的工作流程,在内存中不必要地消耗资源和处理时间的时候。但我们也能亲自进 行控制。这里列出了工作流实例的卸载点,在那些地方可进行持久化。

1.在ActivityExecutionContext 完成并结束(卸载)后。我们在第四章(活动类型和工作流类型介绍)简要谈过ActivityExecutionContext 对象。

2.在Activity 进入空闲状态时。

3.一旦一个TransactionScopeActivity 完成(卸载)时。我们将在第十五章(工作流和事务)看到这个活动。

4.一旦一个带有PersistOnCloseAttribute 属性的Activity 完成。

5.当你明确调用WorkflowInstance.Unload 或WorkflowInstance.TryUnload 时。

通过调用WorkflowInstance 对象的特定方法或通过使用一个Delay 活动让你的工作流进入空闲状态,你可控制工作流实例被持久化的时机。在延时的时候,通过传递一个参数到持久化服务的构造函数中,你将可对自动的持久化行为进行控制。


备注: 暂停工作流实例和对工作流实例进行延时是不相同的。使用Delay 活动将自动地把工作流实例写入数据库(前提是你正使用 SqlWorkflowPersistenceService 并已经对其进行配置,本章的最后一节将看到这些)。

暂停仅仅是使工作流从激活的处理状态中撤 出。然而你也可选择使用Unload 或TryUnload 以手动的方式把工作流写入到数据库中。

WF 是如何完成这些的呢?这通过使用 SqlWorkflowPersistenceService 并结合创建一个特定的数据库来完成这项任务(这非常像我们在前一章创建的跟踪数据库)。你可 使用相关脚本来创建一个数据库的架构(表和视图)以及执行持久化所需的存储过程。首先让我们来创建数据库。


搭建SQL Server以进行持久化


就像前一章一样,我们通过在SQL Server Management Studio Express 中创建一个新数

据库来开始我们的工作。


创建一个SQL Server 2005持久化数据库


1.启动SQL Server Management Studio,连接数据库引擎。

image.png

2.在数据库节点上单击右键激活右键快捷菜单,选择“新数据库”。

3.在新数据库对话框中输入“WorkflowStore”作为数据库的名称字段,点击确定。

4. 下一步将执行WF 为设置持久化所提供的脚本(这会创建表和视图)。这些脚本的位置在<%WINDIR%>\Microsoft.NET \Framework\v3.0\Windows Workflow

Foundation\SQL\ZH-CHS,在这里<%WINDIR%>是指你的Windows 目录(通常是C:\Widows)。

在 SQL Server Management Studio 打开SqlPersistence.Schema.sql 文件。

5.SQL Server Management Studio 会在一个新窗口中导入文件中的脚本,但在我们运行脚本前,我们需指明在哪个数据库中运行这些脚本,因此我们要选择WorkflowStore 数据库。

6.点击工具栏上的执行按钮执行这些脚本。

7.重复4-6 步执行SqlPersistence.Logic.sql 脚本。这将在数据库中创建必须的存储过程。


SqlWorkflowPersistenceServic服务介绍


保存和恢复工作流实例是可选的:假如你不想(持久化)的话,你就可避免使用持久化存储介质(如数据库)来保存工作流实例。因此通过可插拔服务(SqlWorkflowPersistenceService)来实现持久化或许更有意义。当工作流实例正处于运行中的时 侯,WorkflowInstance 和SqlWorkflowPersistenceService 协作工作以执行存储和恢复任务。

表面上,所有这些听起来相对地简单。假如我们需要把工作流实例换出并存储到数据库,我们就通知持久化服务为我们存储它。但假如我们使用单一的数据库来持久化不同进程中运行的工作流会发生什么呢?在工作流实例执行中是怎样进行停止和重启的呢?

使 用单一的数据库来存储工作流实例并不罕见。但每个实例可能在不同的机器不同的进程中执行,假如要保存和稍后恢复工作流实例,我们也必须要有一个手段来存储 工作流在执行时刻实际的系统状态。例如,SqlWorkflowPersistenceService 会存储实例是否被阻塞(等待其它东西),它的状态 (执行中,空闲等等)以及像序列化实例数据和拥有者标识等各种各样的辅助信息。所有这些信息对于在以后的某个时间重现实例是必须的。


我们能够通过WorkflowInstance 对象的三个方法来控制上述的持久化,参看表6-1。


表6-1 WorkflowInstance 方法

image.png

正如表6-1 中所指出的,我们有两个方法来用于卸载和持久化工作流实例。你该使用哪个方法取决于你的代码想要做什么事。Unload 会暂停工作流 实例来为其持久化做好准备。

假如这要花费很长时间,该线程执行Unload 操作也就要等待很长时间。然而,TryUnload 在请求卸载一个执行中的实例 时将立即返回,但这不能保证该工作流实例真正被卸载并持久化到数据库中。为进行检验,你应检查TryUnload 方法的返回值。假如该值是true,该工 作流实例本身就是卸载和持久化了的,假如该值是false,则该工作流实例还没有被卸载和持久化。TryUnload 的优点是你的线程不会处在等待状态, 当然缺点是你可能要对该执行中的工作流实例重复地使用TryUnload 方法(进行检查)。


卸载实例

尽管WF 在特定的时间将卸载和持久化你的工作流实例,但有时候你可能想亲自进行控制。对于这些情况,WorkflowInsance.Unload 和WorkflowInstance.TryUnload 是有助于你的。

假 如你首先没有插入SqlWorkflowPersistenceService 就调用上述两个方法中的任何一个的话,WF 将抛出一个异常。当然,假如有某 种形式的数据库错误,你也将收到一个异

常。因此,好的做法是使用try/catch 块来包围这些调用,以阻止你的整个应用程序(发生异常时)崩溃。(注意 这并不是说做任何事都与异常有关,有时你可能想要忽略它。)

我们来试试吧!我们先创建一个小图形用户界面,它为我们提供了一些按钮,我们使用这些按钮来迫使应用程序产生特定的行为。应用程序的复杂性会增加一点,但我们也将走向

更加真实的应用中。

这 里我们创建的应用程序仍然相对简单。它仅仅有几个按钮,我们在特定的时间点击它们来迫使工作流实例卸载。(在下一节,我们将重新加载它。)我将故意强制一 个长时间运行的工作流卸载,但和至今我们看到过的工作流不同,它将不会使用Delay 活动。这样做的原因就像你或许猜到的一样简单,是因为Delay 活动 很特别,它会使自己自动伴随着持久化。

相反,我会强制我们的工作流实例卸载而不是像Delay 活动那样自动进行卸载。在本章的“在空闲中加载和卸载实例” 这一节我们将看到Delay 活动和它们的作用。当前,我们将请求工作流线程休眠(sleep)10 秒,以便为我们提供充足的时间来按下我们程序中的按钮中 的一个。


创建一个新的宿主应用程序

1.就像你在前一章做的一样,打开Visual Studio 创建一个新应用程序项目。但是,不是要创建一个基于控制台的应用程序,而是创建一个Windows 应用程序,名称为

WorkflowPersister。下面的步骤在第二章中已经描述过:包含“添加工作流assembly 引用”、“宿主工作流运行时”、“创建 WorkflowRuntime 工厂对象”,“启动工作流运行时”,“停止工作流运行时”,“使用工作流运行时工厂对象”,“处理工作流运行时事件”过 程。

最后,添加一个app.config 文件(可参考前一章中的“添加SqlTrackingService 到你的工作流中”,可不要忘记添加 System.Configuration 的引用)。


image.png

2.现在向app.config 文件中添加恰当的数据库连接字符串(数据库为WorkflowStore)。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="StorageDatabase" connectionString="Data Source=(local)\SQLEXPRESS;Initial Catal
og=WorkflowStore;Integrated Security=True;"/>
</connectionStrings>
</configuration>

3.当你创建了WorkflowPersister 项目时,Visual Studio 显示了Windows Forms 视图设计器。在Windows Forms 视图设计器中把鼠标移到工具箱上,选择一个Button 控件,并把它拖放到设计器的界面上。

4.我们将为这个按钮设置一些富有意义的文字属性,以便于我们知道我们点击的是什么。

选中这个按钮,然后在Visual Studio 的属性面板中选择该按钮的Text 属性,把该属性的值设置为“Start Workflow”。

5.为该按钮添加Click 事件的处理程序,具体代码将在后面的步骤中添加。

6.修改按钮的位置和大小,如下图所示:

image.png

7.重复步骤3 至步骤5,再添加两个按钮,一个的text 属性为“Unload Workflow”,

另一个的text 属性为“Load Workflow”。如下图所示:

image.png

8.现在就为测试我们的工作流创建好了用户界面,该是为我们将执行的应用程序添加事件处理代码的时候了。当应用程序加载时我们需要初始化一些东西,做这些工作的一个很合

适的地方是在主应用程序窗体中的Load 事件处理程序。

image.png

9.在该事件处理程序(处理方法)中输入下面的代码:

_runtime = WorkflowFactory.GetWorkflowRuntime();
_runtime.WorkflowCompleted +=
new EventHandler<WorkflowCompletedEventArgs>(Runtime_WorkflowCompleted);
_runtime.WorkflowTerminated +=
new EventHandler<WorkflowTerminatedEventArgs>(Runtime_WorkflowTerminated);

10。在Form1 类中声明下面名称为_runtime 的字段:

protected WorkflowRuntime _runtime = null;

protected WorkflowInstance _instance = null;

11.添加System.Workflow.Runtime、System.Workflow.ComponentModel 和

System.Workflow.Activity 三个工作流组件的引用(可参考前面章节),然后在该代码文件中添加下面的命名空间:

using System.Workflow.Runtime;

12.尽管我们现在有了一个应用程序来宿主工作流运行时,但它实际上没做任何事。为完成些功能,我们需向按钮的事件处理中添加一些代码。先向button1_Click 中添加下面的代

码:

button2.Enabled = true;
button1.Enabled = false;
_instance = _runtime.CreateWorkflow(typeof(PersistedWorkflow.Workflow1));
_instance.Start();

这些代码使“Start Workflow”按钮禁用,而让“Unload Workflow”按钮可用,然后启动了一个新的工作流实例。

13. 下一步,找到“Unload WorkflowInstance”按钮的事件处理:button2_Click,然后添加下面的代码。这里,我们使用 WorkflowInstance.Unload 方法来卸载工作流实例并把它写入数据库。在工作流实例卸载后,我们让“Load Workflow”按钮可用。注意假如我们在卸载工作流实例时产生异常,“Load Workflow”按钮是不可使用的。这样做的意义是:假如卸载请求失败,也就不用再加载。

button2.Enabled = false;
try
{
    _instance.Unload();
    button3.Enabled = true;
} // try
catch (Exception ex)
{
    MessageBox.Show(String.Format("Exception while unloading workflow" +
    " instance: '{0}'",ex.Message));
} // catch123

备注:牢记WorkflowInstance.Unload 是同步的,虽然我在本章前面已经阐述过,但 这点很重要。这意味着线程试图卸载工作流实例时将会被阻塞(暂停),直到操作完成后为止(不管卸载实例时是成功还是失败)。在这种情况下,可准确执行我想 要的行为(指卸载),因为我不想反复查看实例是否被卸载。但有时,你想在卸载时不会被阻塞,就应使用前面说过的 Workflowinstance.TryUnload。稍后,在你添加完最后一批代码并运行应用程序时,当你单击“Unload Workflow”时密切观察,你会看到应用程序会简短地被冻结,因为它正等待工作流卸载。

14.现在回到我们关注的工作流事件处理 上:Runtime_WorkflowCompleted 和Runtime_WorkflowTerminated。这两个事件处理实际上完成相同的动 作,那就是重置应用程

序以便为另一个工作流实例的执行做好准备。在“button2”的“click”事件处理方法(该方法包含代码我们已在前面的步骤中 添加)的下面添加下面这些方法:

void Runtime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
{
    WorkflowCompleted();
}
void Runtime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
{
    WorkflowCompleted();
}

15.当然,我们现在还要创建“WorkflowCompleted”方法。假如你熟悉Windows 编程,你可能知道Windows 一直 以来存在着的限制问题。这个限制简单的说就是你不能在创建窗口控件的线程外的任何线程中改变该窗口控件的状态。因此假如你想改变某个控件的text,你必 须在创建该控件的同一线程上指定该控件的text,使用任何其它线程最有可能导致的结果是造成你的应用程序崩溃。因此我们即将加入的代码对你来说可能很搞 笑,但所有要做的这些的真正目的是确信我们是在原来的创建按钮的线程中来使按钮能用和禁用。(事件处理几乎总是在不同的线程上进行调用。)假如我们只是在 按钮自身的事件处理中使其可用,应用程序可能还是能工作,但它更大的可能是崩溃和挂起。简单地把下面的代码复制并放到源文件Form1 类的尾部,应用程序 才会正确的工作。

private delegate void WorkflowCompletedDelegate();
private void WorkflowCompleted()
{
    if (this.InvokeRequired)
    {
    // Wrong thread, so switch to the UI thread
    WorkflowCompletedDelegate d = delegate() { WorkflowCompleted(); };
    this.Invoke(d);
} // if
else
{
    button1.Enabled = true;
    button2.Enabled = false;
    button3.Enabled = false;
} // else
}

16.在创建我们将执行的工作流之前我们需要最后做一件事,那就是要修改WorkflowFactory 类。假如你从第五章(“为你的工作 流添加跟踪服务”)以来准确地遵循

了所有的步骤来创建和修改WorkflowFactory 的话,你实际上创建了一个为工作流运行时提供跟踪服务的工厂对 象。我们将对该代码进行轻微的调整,把SqlTrackingService 服务改为SqlWorkingPersistenceService,并且改 变声明命名空间的语句(把System.Workflow.Runtime.Tracking 改为 System.Workflow.Runtime.Hosting)。打开

WorkflowFactory.cs 文件进行编辑。

17.用下面的代码来替换声明的System.Workflow.Runtime.Tracking 命名空间。

using System.Workflow.Runtime.Hosting;

using System.Configuration;

18。最后为运行时添加持久化服务,方法是在创建工作流运行时对象后添加下面的代码:

string conn = ConfigurationManager.ConnectionStrings["StorageDatabase"].ConnectionString;
_workflowRuntime.AddService(new SqlWorkflowPersistenceService(conn));


注意:因为我们在代码中创建的工作流实例的类型是PersistedWorkflow.Workflow1 类型(在第12 步中),因此我们的宿主应用程序编译并执行会出错,我们将在下面的一节解决。这里我们就有了一个Windows 图形用户界面和宿主应用程序,我们使用它们来承载我们的工作流。谈到工作流,我们不是要创建并执行它吗?其实,这在下面进行讲解。


创建一个新的可卸载的工作流

1. 像前面一章一样,我们又将在我们的项目中创建一个新的顺序工作流库。在VisualStudio 中激活WorkflowPersister 应用程序,然后点击“文件”菜单,选择“添加”,当子

菜单弹出后,选择“新建项目”。从“添加新项 目”的对话框中添加一个“顺序工作流库”的项目,项目名称为“PersistedWorkflow”。

2.在应用程序解决方案中创建并添 加一个新项目后,将呈现该工作流的视图设计器界面。从工具箱中拖拽一个“Code”活动到设计器界面上。在Visual Studio 属性面板上设置

“Code”活动的“ExecuteCode”属性为PreUnload 然后按下回车键。

3.然后Visual Studio 将自动切换到该工作流的源代码文件中,向刚刚插入的PreUnload方法添加下面的代码:

_started = DateTime.Now;
System.Diagnostics.Trace.WriteLine(String.Format("*** Workflow {0} started: {1}",
WorkflowInstanceId.ToString(),
_started.ToString("MM/dd/yyyy hh:mm:ss.fff")));
System.Threading.Thread.Sleep(10000); // 10 seconds

4.为了计算工作流消耗的时间(下面的步骤中我们将看到,该时间至少在两个Code 活动执行的时间之间),我在一个名称为“_started”字段中保存了启动时间。在你的源文件中

构造函数的上面添加该字段:

private DateTime _started = DateTime.MinValue;

5.现在切换到设计视图,添加第二个Code 活动。该活动的ExecuteCode 属性设置为“PostUnload”,并自动生成该方法。你将看到的设计器界面如下:

image.png

6.再次切换回工作流的源代码文件中,向PostUnload 方法中添加下面必要的代码:

DateTime ended = DateTime.Now;
TimeSpan duration = ended.Subtract(_started);
System.Diagnostics.Trace.WriteLine(
String.Format("*** Workflow {0} completed: {1}, duration: {2}",
WorkflowInstanceId.ToString(),
ended.ToString("MM/dd/yyyy hh:mm:ss.fff"),
duration.ToString()));

7.最后一步是添加一个项目级的引用把该工作流项目引用到我们的主应用程序中,具体步骤可参考前面的章节。


备注: 现在你或许想运行该程序,但请等等!假如你运行该应用程序,然后点击“StartWorkflow”按钮而没有点击“Unload Workflow”按钮的话,该应用程序不会出现运行错误。因为一旦工作流被卸载,但我们还没有添加代码来重新加载这个已被持久化(卸载)的工作流实例。 因此在下一节添加代码前,你不应单击“Unload Workflow”按钮。

这样做的目的是在工作流开始执行时,在第一个Code 活动 中让它休眠(sleep)10 秒钟。在此期间,你可点击“Unload Workflow”按钮来卸载该工作流。在这10 秒届满后,该工作流将被卸载并持久化到数据库中。这些事一旦发生,你就可喝杯咖啡、吃吃棒棒糖或者其它任 何事休息休息:你的工作流已被保存到数据库中,正等待再次加载。让我们看看这又是怎么工作的。


加载实例


WorkflowInstance 公开了二个卸载方法:“Unload”和“TryUnload”,但仅仅只有一个“Load”方法,该方法不用关心工作流实例是怎样存储到数据库中的。一 旦它(工作流实例)被存储,你就可使用WorkflowInstance.Load 来把它再次重置到执行状态。现在我们将向 WorkflowPersister 应用程序中添加合适的代码来做这些事情。


加载被持久化的工作流

1.在Visual Studio 中打开WorkflowPersister 应用程序,打开该源代码文件,找到主应用程序窗体,定位到“button3_Click”事件处理方法。

2.在“button3_Click”事件处理中添加下面的代码:

button3.Enabled = false;
try
{
    _instance.Load();
} // try
catch (Exception ex)
{
    MessageBox.Show(String.Format("Exception while loading workflow" +
    " instance: '{0}'", ex.Message));
} // catch
button1.Enabled = true;

现在我们来看看所有这些能否真正工作。我们将运行两次来测试工作流:一次我们直接运行到结束,一次我们将强制其卸载。然后我们比较执行时间并看看SQL Server 数据库内记

录了些什么。


测试WorkflowPersisiter应用程序


1.按下F5 键调试WorkflowPersisiter 应用程序,如有任何编译错误,请进行修正。注意该测试我们会写一些输出的跟踪信息到“输出”窗口中,因此假如没有“输出”窗口的话,

在Visual Studio 的“视图”菜单下选择“输出”使其呈现。

2. 点击“Start Workflow”按钮创建并启动一个工作流实例,然后该“Start

Workflow”按钮会被禁用,而“Unload Workflow”按钮将能使用。因为我们让工作流线程休眠10 秒钟,经过10 秒后,“Unload Workflow”按钮又会自动禁用,“Start Workflow”按钮则重新可用。在本测试中,工作流会一直运行到结束,工作流总共执行的持续时间会是10秒。

3.再一次点击 “Start Workflow”按钮。但是,这次在这10 秒的休眠期间将点击

“Unload Workflow”按钮。该按钮在该10 秒期间内将会被禁用,过后,“Load Workflow”按钮将能使用。在此时,你的工作流被实例化并保持被卸载状态,直到你重新加载它。

4.但在你重新加载该工作流实例前,请打开WorkflowStore 数据库中的InstanceState表,会在该表中看到一行记录,这行就是你的被持久化的工作流实例!

image.png

5.回到WorkflowPersister 程序的执行当中,点击“Load Workflow”按钮。“LoadWorkflow”按钮会被禁用,而“Start Workflow”按钮将能使用。

6.结束WorkflowPersister 应用程序。

7.Visual Studio 输出窗口中会包含和我们执行的两个工作流有关的信息。

8.在输出窗口中滚动到底部,查找我们注入的文本(我们使用三个星号“***”来装饰这些文本)。如下所示:


image.png

假 如你回头看看InstanceState 表的截图,并把你看到的工作流实例ID 和你在VisualStudio 输出窗口中看到的工作流实例ID 做比较,你会看到在我们的例子中有两个相同的实

例ID:bfb4e741-463c-4e85-a9e0- c493508ec4f1。该实例花费的时间为:00:01:41.4859296,第一个工作流实例(ID 为:Workflow dab11c11-9534-4097-b5bc-fd4e96cfa66c)花费的时间正如我们期望的,几乎就为10 秒钟。两个实例ID 和执行时间有区 别,但实质上是一样的。

你卸载并持久化到数据库中的工作流实例的运行时间将超过10 秒,在InstanceState 表中显示的实例ID 会和Visual Studio 输出窗口中显示的相匹配。


在空闲时加载和卸载实例


我 在本章的前面部分曾经提到,在我们的工作流过程中我们将使用System.Threading.Thread.Sleep 来替换Delay 活动,以进行工 作流的测试。在那时我说过,

我选择这样做的原因。就持久化而言,Delay 活动有一些特殊的处理能力。我们现在就来简要地看看Delay 会为我们做些什 么。

假如你在你的工作流中引入了一个Delay 活动,那目的明显是要暂停处理一段时间,不管这是一个确切的时间段还是一个不确定的暂停,该暂停会在将来的某个特定时间点,如五

天后被取消。

当 执行一个Delay 活动时,假如工作流运行时已经附加了SqlWorkflowPersistenceService 服务的话,该工作流运行时将会自动地 为你持久化该工作流,而且当延时周期过期后又自动恢复它。注意,不管运行工作流运行时的系统是否被关闭、重启等等上述事件都会发生。为了使之能自动持久 化,你要在创建你的工作流运行时为SqlWorkflowPersistenceService 服务添加一个特定的构造函数参数。(之前的例子省略了这 些,因此工作流不会自动进行持久化。)

我提到的构造函数参数能使SqlWorkflowPersistenceService 的 internal 方法“UnloadOnIdle”在工作流实例空闲时被调用。该方法通常不会被调用。你必须通过使用

SqlWorkflowPersistenceService 构造函数中的一个重载函数来明确指定这一点。在下面的例子中,将使用一个集合参数,因为你既 想传入连接字符串也想传入空闲卸载标志。甚至还有其它更加灵活的构造函数(在本例中我们只描述这一个)。现在我们就来看看这个工作流会自动持久化的例子。


创建一个新的在空闲时持久化的工作流


1. 对于这个例子,为了让你快速领悟到在空闲时持久化是怎样工作的,我们将使用一个简单的基于控制台的应用程序,打开Visual Studio,创建一个新的Windows 项目,该控制

台应用程序的名称命名为“WorkflowIdler”。下面的步骤,如“添加对工作流模块的引用 ”、“宿主工作流运行时”,“创建WorkflowRuntime 工厂对象”、“启动工作流运行时”、“停止工作流运行时”、“使用工作流运行时工厂对象 ”及“处理工作流运行时事件”等过程来自第二章。

2.就像在前面例子(“创建一个新的宿主应用程序”)中的第16 步和第17 步一样,修改WorkflowFactory 类。但是,还有一些额外的修改工作是必要的,添加下面的语句:

using System.Collections.Specialized;

3.和前面例子(“创建一个新的宿主应用程序”)中的第18 步一样,在运行时对象被创建后添加持久化服务:

NameValueCollection parms = new NameValueCollection();
parms.Add("UnloadOnIdle", "true");
parms.Add("ConnectionString", ConfigurationManager.ConnectionStrings["StorageDatabase"].Connecti
onString);
_workflowRuntime.AddService(new SqlWorkflowPersistenceService(parms));

4.和前面的例子一样添加一个应用程序配置文件(连接字符串仍然相同)。具体过程可参考第五章中的“为你的工作流添加跟踪服务”,那里添加了该app.config 文件。

5.创建一个单独的顺序工作流库项目来承载我们的新工作流,工作流库的名称为IdledWorkflow。

6.重复前一个名称为“创建一个新的可卸载的工作流”例子中的步骤2 到步骤4。这些步骤放置了二个Code 活动到你的工作流中。

7.在源代码文件中添加下面的代码到“PreUnload”方法中(先前一步你已添加了“PostUnload”方法的代码)。

_started = DateTime.Now;
System.Diagnostics.Trace.WriteLine(
String.Format("*** Workflow {0} started: {1}",
WorkflowInstanceId.ToString(),
_started.ToString("MM/dd/yyyy hh:mm:ss.fff")));

8.返回到工作流视图设计器上,拖拽一个Delay 活动到两个code 活动之间。

image.png

9.指定Delay 活动的“TimeoutDuration”属性为30 秒。这将有充足的时间来查看WorkflowStore 数据库中的InstanceState 表。

10.工作流现在就设计完成了,向WorkflowIdler 应用程序中添加对该工作流的项目引用。

11.在WorkflowIdler 项目中打开Program.cs 文件,找到下面的这行代码:

Console.WriteLine("Waiting for workflow completion.");

12.当然,因为没有工作流被启动,该应用程序也就不会等待工作流完成。因此,创建一个工作流实例,在你找到的那行代码下添加下面的代码:

// Create the workflow instance.
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(IdledWorkflow.Workflow1));
// Start the workflow instance.
instance.Start();

13.按F6 键编译该解决方案,纠正任何弹出的编译错误。现在,当你执行该WorkflowIdler应用程序时,Delay 活动将强制工 作流实例持久化到存储数据库中。然而,你会等上超过30秒(多达2 分钟)的时间,实例才被重新加载。那是因为工作流运行时在空闲状态下由于延时,要周期性 地对持久化工作流进行检查,但它不能保证那些工作流将仅仅等上所期望的延时时间。WF 会周期性地轮询数据库,寻找那些正等待计时器事件的空闲并已被持久化 的工作流(Delay 活动使用了一个计时器)。默认的轮询时间是2 分钟。


备注:可以更改默认的数据库 轮询时间,方法是使用SqlWorkflowPersistenceService服务时为其提供一个TimeSpan,可使用四个参数的构造函数(分别 是连接字符串、工作流

处于空闲状态时是否卸载的标志、工作流保持锁定的时间长度以及持久化服务轮询数据库以查找计时器已过期的工作流的频率)。


源码下载 http://files.cnblogs.com/gyche/WF%20Step%20by%20Step/Chapter6.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