真实的工作流程中,许多处理过程都要花费很长时间。但在服务器上,这些工作流程序如果保持长时间的资源占用,线程等上几分钟、几小时甚至几天,这对有限的服务器资源是不能接受的。因此WF中需要提供将长时间运行且空新的工作流暂时脱机的机制。WF使用数据库作为存储介质,集成了一个可纳入我们的工作流并使其持久化的机制。还有一种原因是工作流完全在内存处理,如果系统突然关闭将会发生意外,需要我们持久化它们。
持久化工作流实例
WF可以为我们加载和卸载工作流实例,它支持一个服务:SqlWorkflowPersistenceService,用来把工作流序列化进SQL Server数据库。
在内存中不必要的消耗资源和处理时间时,工作流需卸载并进行持久化。有如下卸载点:
ActivityExecutionContext完成并结束后;Activity进入空闲状态时;一个TransactionScopeActivity完成时;明确调用WorkflowInstance.Unload或WorkflowInstance.TryUnload时。
一个例子
建数据库
通过使用SqlWorkflowPersistenceService并结合创建一个特定的数据库来说明这个过程。像之前一样首先创建数据库。我们使用与上文相同的Windows目录下的SqlPersistenceService_Schema和SqlPersistenceService_Logic两个脚本。首先创建数据库WorkflowStore,在其中执行两个脚本。
关于SqlWorkflowPersistenceService服务
工作流处于运行中时,WorkflowInstance和SqlWorkflowPersistenceService协同工作以执行存储和恢复任务。
经常会用单一的数据库来存储多个在不同机器不同进程上的工作流实例,如果要保存和恢复,需要有一种方法来存储工作流在执行时刻的系统状态,如实例是否被阻塞,它的状态(执行中,空闲等),序列化实例数据和拥有者标识等辅助信息。这些信息在恢复时是必须的。
WorkflowInstance对象有三个方法来控制上述持久化:
方法 | 功能 |
Load | 加载先前被卸载(持久化)的工作流实例 |
TryUnload | 试图从内存中卸载(持久化)该工作流实例。和调用 Unload不同的是,调用 TryUnload 时假如工作流实例不能立即被卸载,那它将不会被阻塞(维持执行状态)。 |
Unload | 从内存中卸载(持久化)该工作流实例。注意该方法为进行卸载将阻塞当前执行的线程,直到工作流实例被真正地卸载。这可以是一个漫长的操作,这取决于个人的工作流任务。 |
TryUload和Unload方法视你的代码要求,二者各有优缺点。
卸载实例
WF在特定的时间会卸载和持久化工作流实例。如果你想手动控制,使用上述两个方法。但如果没有先插入SqlWorkflowPersistenceService就调用上述两个方法之一,WF会抛异常。如果有数据库错误,也会有异常。因此使用try/catch块来包围这些调用以阻止程序崩溃。
我们用一个WinForm程序加以说明。
打开VS新建一个Windows应用程序,名为WindowPersister。
如前几节所述的步骤,进行如下工作:
在主程序中添加工作流引用;创建工作流运行时工厂对象;启动工作流运行时;使用工作流工厂对象;处理工作流运行时事件。
添加一个App.config文件,并添加:
<?xml version="1.0"encoding="utf-8" ?>
<configuration>
<connectionStrings>
<addname="StorageDatabase"connectionString="DataSource=(local)\SQLEXPRESS;Initial Catal
og=WorkflowStore;Integrated Security=True;"/>
</connectionStrings>
</configuration>
设计Form,如下图:
我们在主窗体的Load事件添加名为Form1_Load方法,并添加如下代码:
_runtime = WorkflowFactory.GetWorkflowRuntime();
_runtime.WorkflowCompleted +=
newEventHandler<WorkflowCompletedEventArgs>(Runtime_WorkflowCompleted);
_runtime.WorkflowTerminated+=
newEventHandler<WorkflowTerminatedEventArgs>(Runtime_WorkflowTerminated);
加如下字段:
protected WorkflowRuntime _runtime = null;
protected WorkflowInstance _instance = null;
添加 System.Workflow.Runtime、System.Workflow.ComponentModel和System.Workflow.Activity 三个工作流组件的引用,并添加运行时命名空间。
现在我们就有了一个Windows Form来宿主工作流运行时。
向Start Workflow按钮事件添加一些代码:
button2.Enabled= true;
button1.Enabled = false;
_instance = _runtime.CreateWorkflow(typeof(PersistedWorkflow.Workflow1));
_instance.Start();
启动了工作流实例。
添加Unload WorkflowInstance按钮的事件处理:button2_onclick。我们使用WorkflowInstance.Unload方法来卸载工作流实例并把它写入数据库。卸载完成后,我们让"LoadWorkflow"按钮可用,但如果卸载产生异常,Load Workflow按钮也是不可用的。
接下来回到工作流事件处理上,对完成和中止事件添加相同的代码:
WorkflowCompleted();
这是一个未定义的方法。具体代码如下:
private voidWorkflowCompleted()
{
if (this.InvokeRequired)
{
WorkflowCompletedDelegated = new delegate() { WorkflowCompleted(); };
this.Invoke(d);
}
else
{
button1.Enabled = true;
button2.Enabled = false;
button3.Enbaled = flase;
}
}
并添加:private delegate voidWorkflowCompletedDelegate();
上述代码是在工作流停止或中止后让三个按钮变为初始的状态。但是看起来有些费解(对委托不熟的初学者可以借机复习一下),有一点需要注意,即UI线程不允许其他线程对其进行修改,所以上述代码的加入了对当前线程的判断,确保在UI线程上修改按钮状态,否则有可能会产生程序崩溃。
最后我们还要在WorkflowFactory中添加SqlWorkflowPersistenceService,添加命名空间,System.Workflow.Runtime.Hosting。
在工作流运行是对象后添加下如下代码:
string conn = ConfigurationManager.ConnectionStrings["StorageDatabase"].ConnectionString;
_workflowRuntime.AddService(newSqlPersistenceService(conn));
到现在我们还缺一个工作流以便在宿主中执行。像上节一样,创建一个新的工作流顺序库。项目名称为PersistedWorkflow。我们向设计器中添加两个Code活动。它们的ExecuteCode属性分别设为PreUnload和PostUnload,从而在Workflow1.cs中生成了这两个方法。
PreUnload中添加:
_started= DateTime.Now;
System.Diagnostics.Trace.WriteLine(String.Format("*** Workflow {0}started: {1}",
WorkflowInstanceId.ToString(),
_started.ToString("MM/dd/yyyyhh:mm:ss.fff")));
System.Threading.Thread.Sleep(10000);
在PostCode中添加:
DateTimeended = 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/yyyyhh:mm:ss.fff"),
duration.ToString()));
向WorkflowPersister添加工作流的引用。
加载实例
一旦工作流被存储,可使用Load方法把它再次重置到执行状态。
在WorkflowPersister的Form1.cs的button3_Click事件处理中添加:
button3.Enabled = false;
try
{
_instance.Load();
} // try
catch(Exception ex)
{
MessageBox.Show(String.Format("Exception whileloading workflow"+
" instance: '{0}'", ex.Message));
} // catch
button1.Enabled = true;
编译,运行。可启动、卸载和加载工作流了。调试时可在输出窗口看到我们输出的文本。
空闲时加载和卸载实例
在上面的工作流中我们使用的是System.Threading.Thread.Sleep代替Delay活动。Delay活动执行时,如果工作流已附加了SqlWorkflowPersistenceService服务,该工作流运行时自动地为你持久化该工作流,延时周期过后又恢复它,为了使之能够自动持久化,你要为SqlWorkflowPersistenceService添加一个特定的构造函数参数。构造函数参数能使SqlWorkflowPersistenceService的internal方法UnloadOnIdle在工作流空闲时被调用。下面使用一个集合参数,它包括了连接字符串和空闲卸载标志。
我们可使用上例的代码说明,作一些修改。
在WorkflowFactory类里,添加using System.Collections.Specialized;
在运行时对象被创立后添加持久化服务:
NameValueCollection parms = newNameValueCollection();
parms.Add("UnloadOnIdle","true");
parms.Add("ConnectionString",ConfigurationManager.ConnectionStrings["StorageDatabase"].Connecti
onString);
_workflowRuntime.AddService(newSqlWorkflowPersistenceService(parms));
在工作流项目中,把最后一句System.Threading.Thread.Sleep删除。在两个Code活动之间加一个Delay活动,TimeoutDuration设置为30秒
编译,运行。