第三步:运行工作流
回顾上一部分中创建的工作流实例,看看其中我们创建的变量和参数。
我们在StateMachineNumberGuessWorkflow定义了两个变量和两个参数,分别如下:
变量Guess和Target:存储用户输入的猜测值和目标值,在后面的State中会多次用到。
输入参数MaxNumber:接受输入的猜测值上限
输出参数Turns:返回猜测的次数
WorkflowApplication.ResumeBookmark方法被调用后,NativeActivity将被恢复,我们在ReadInt.Execute()中指定的回调函数OnReadComplete BookmarkCallback将会被调用。 方法OnReadComplete的state参数就是ResumeBookmark方法的第二个参数,也就是用户输入的猜测值。
运行工作流的代码如下:
using System; using System.Activities; using System.Collections.Generic; using NumberGuessWorkflowActivities; namespace DirectWorkflowHost { class Program { private static WorkflowApplication wfApp = null; static bool isCorrect = false; static void Main(string[] args) { NewGame(); EnterGuess(); Console.Read(); } //循环猜值 private static void EnterGuess() { while (isCorrect == false) { int guess = Convert.ToInt32(Console.ReadLine()); //恢复具有名称为EnterGuess的书签 wfApp.ResumeBookmark("EnterGuess", guess); } } //建立新游戏 private static void NewGame() { var inputs = new Dictionary<string, object>(); inputs.Add("MaxNumber", 20);//"MaxNumber"和StateMachineNumberGuessWorkflow的名为MaxNumber的输入参数对应 WorkflowIdentity identity = new WorkflowIdentity() { Name = "StateMachineNumberGuessWorkflow", //使用指定的主版本号、次版本号、内部版本号和修订号初始化 System.Version 类的新实例。 Version = new Version(1, 2, 3, 4) }; //创建活动 Activity wf = new StateMachineNumberGuessWorkflow(); //为工作流的单个实例提供宿主 wfApp = new WorkflowApplication(wf, inputs, identity); ConfigureWorkflowApplication(wfApp); wfApp.Run(); } private static void ConfigureWorkflowApplication(WorkflowApplication wfApp) { //获取或设置工作流实例完成时调用的 System.Action<T>。 wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e) { //活动处于出错状态。 if (e.CompletionState == ActivityInstanceState.Faulted) { Console.WriteLine(string.Format("Workflow Terminated. Exception:{0}\r\n{1}", e.TerminationException.GetType().FullName, e.TerminationException.Message)); } //活动处于已取消状态 else if (e.CompletionState == ActivityInstanceState.Canceled) { Console.WriteLine("Workflow Canceled."); } else { //获取工作流实例的输出参数 int turns = Convert.ToInt32(e.Outputs["Turns"]); Console.WriteLine(string.Format("Congratulations, you guessed the number in {0} turns.", turns)); //当猜值成功后,工作流停止,结束程序 isCorrect = true; } }; } } }
运行结果如下:
第四部、持久化工作流实例
1、使用持久化方式保存工作流实例时首先创建一个数据库WF45GettingStartedTutorial。
2、创建相关表。创建表的脚步存放在一下路径:C:\Windows\Microsoft.NET\Framework\v4.0.30319\SQL\en,文件一共有两个,分别是:SqlWorkflowInstanceStoreSchema.sql和SqlWorkflowInstanceStoreSchema.sql,依次运行这两个脚本,会创建架构和表,并向表中插入一些基础数据。
3、通过持久化,可以将工作流实例WorkflowApplicationInstance的信息保存到数据库,所以我们可以先创建一个工作流宿主WorkflowApplication wfApp1,调用wfApp.Run()方法运行工作流,在wfApp.PersistableIdle事件触发时,自动调用关联的InstanceStore对象保存WorkflowApplicationInstance信息。
保存成功后,我们可以在任何时间创建新的工作流宿主WorkflowApplication wfApp2,重新从存储区中提取WorkflowApplicationInstance信息 instance,然后让wfApp2加载instancewfApp2.Load(instance); 这样就可以获取原来执行了部分流程的工作流实例,继续进行我们的操作。
工作流实例是同一个实例,但是宿主可以不同。为了方便,现在使用一个Winform窗体项目PersistWorkflowHost进行演示。
代码如下:
using System;
using System.Activities;
using System.Activities.DurableInstancing;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Windows.Forms;
using NumberGuessWorkflowActivities;
namespace PersistWorkflowHost
{
public partial class UnLoadWorkflowHostForm : Form
{
const string connectionString = "Server=.;Initial Catalog=WF45GettingStartedTutorial;integrated security=true";
SqlWorkflowInstanceStore store;
bool workflowStarting;
/// <summary> 当前工作流实例Id </summary>
public Guid WorkflowInstanceId
{
get
{
return this.cboInstanceId.SelectedIndex == -1 ? Guid.Empty : (Guid)cboInstanceId.SelectedItem;
}
}
public UnLoadWorkflowHostForm()
{
InitializeComponent();
}
private void WorkflowHostForm_Load(object sender, EventArgs e)
{
store = new SqlWorkflowInstanceStore(connectionString);
//CreateDefaultInstanceOwner使用指定实例存储、定义标识和标识筛选器和超时间隔,创建工作流的默认实例所有者。
WorkflowApplication.CreateDefaultInstanceOwner(store, null, WorkflowIdentityFilter.Any);
cboRange.SelectedIndex = 0;
if (cboInstanceId.Items.Count > 0)
cboInstanceId.SelectedIndex = 0;
ListPersistenceWorkflows();
}
/// <summary>列出所有已经保存的工作流实例Id</summary>
private void ListPersistenceWorkflows()
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
//持久化后的工作流数据保存在Instances表中
string cmdText = "Select [InstanceId] from [System.Activities.DurableInstancing].[Instances] Order By [CreationTime]";
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = cmdText;
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while (reader.Read())
{
Guid id = Guid.Parse(reader[0].ToString());
cboInstanceId.Items.Add(id);
}
}
}
}
private void cboInstanceId_SelectedIndexChanged(object sender, EventArgs e)
{
if (cboInstanceId.SelectedItem == null)
return;
txtStatus.Clear();
if (!workflowStarting)
{
//WorkflowApplicationInstance指定有关工作流应用程序实例的信息。
WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(this.WorkflowInstanceId, store);
lblVersion.Text = instance.InstanceId.ToString();
//使状态机转换到放弃状态。由于调用GetInstance方法后,该工作流应用程序实例将被工作流宿主锁定。
//所以需要调用Abandon方法做一个类似解锁的操作。否则在后续的EnterGuess_Click方法中就无法从存储区中获取工作流实例信息了。
instance.Abandon();
}
}
private void NewGame_Click(object sender, EventArgs e)
{
//创建工作流运行时所需的输入参数
var inputs = new Dictionary<string, object>();
inputs.Add("MaxNumber", Convert.ToInt32(cboRange.SelectedItem));
WorkflowIdentity identity = new WorkflowIdentity()
{
Name = "StateMachineNumberGuessWorkflow",
//主版本号,次要版本号,内建版本号和修订号
Version = new Version(1, 2, 3, 4)
};
Activity wf = new StateMachineNumberGuessWorkflow();
WorkflowApplication wfApp = new WorkflowApplication(wf, inputs, identity);
//由于工作流实例还未保存到存储区(数据库),所以当cboInstanceId的SelectedIndexChanged事件被触发时,还
//不能调用WorkflowApplication.GetInstance去从存储区读取实例信息。所以将workflowStarting设为true值。
//工作流宿主会保存到存储区的LockOwnersTable表
workflowStarting = true;
cboInstanceId.SelectedIndex = cboInstanceId.Items.Add(wfApp.Id);
lblVersion.Text = identity.ToString();
workflowStarting = false;
ConfigureWorkflowApplication(wfApp);
wfApp.Run();
}
//此方法配置 WorkflowApplication,添加所需扩展,并添加工作流生命周期事件的处理程序。
private void ConfigureWorkflowApplication(WorkflowApplication wfApp)
{
wfApp.InstanceStore = store;
//接下来创建 StringWriter 实例,并将它添加到 WorkflowApplication 的 Extensions 集合中。 在将 StringWriter 添加到扩展中时,
//它会捕获所有 WriteLine 活动输出 。在工作流进入空闲状态时,可从 StringWriter 中提取 WriteLine 输出,并显示在窗体上。
StringWriter sw = new StringWriter();
wfApp.Extensions.Add(sw);
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
if (e.CompletionState == ActivityInstanceState.Faulted)
{
UpdateStatus(string.Format("Workflow Terminated. Exception:{0}\r\n{1}",
e.TerminationException.GetType().FullName,
e.TerminationException.Message));
}
else if (e.CompletionState == ActivityInstanceState.Canceled)
{
UpdateStatus("Workflow Canceled.");
}
else
{
int turns = Convert.ToInt32(e.Outputs["Turns"]);
UpdateStatus(string.Format("Congratulations, you guessed the number in {0} turns.", turns));
}
GameOver();
};
//wfApp.Aborted 获取或设置中止工作流实例时调用的 System.Action<T>。
wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
{
UpdateStatus(string.Format("Workflow Aborted. Exception:{0}\r\n{1}",
e.Reason.GetType().FullName, e.Reason.Message));
};
//wfApp.OnUnhandledException获取或设置当前工作流实例遇到未处理的异常时调用的 System.Func<T,TResult>。
wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
UpdateStatus(string.Format("Unhandled Exception:{0}\r\n{1}",
e.UnhandledException.GetType().FullName, e.UnhandledException.Message));
GameOver();
return UnhandledExceptionAction.Terminate;
};
//wfApp.PersistableIdle 获取或设置当前工作流实例处于空闲状态并可被保留时调用的 System.Activities.ActivityFunc。
wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)
{
//Send the current WriteLine outputs to the status window.
var writers = e.GetInstanceExtensions<StringWriter>();
foreach (var writer in writers)
{
UpdateStatus(writer.ToString());
}
// 摘要:enum PersistableIdleAction 指定在允许持久性的情况下,当工作流进入空闲状态时发生的操作。
// None = 0, 指定不执行任何操作。
// Unload = 1, 指定 System.Activities.WorkflowApplication 应保持并卸载工作流。
// Persist = 2 指定 System.Activities.WorkflowApplication 应保持工作流。
return PersistableIdleAction.Unload;
};
}
private delegate void UpdateStatusDelegate(string msg);
/// <summary>使用当前正在运行的工作流的状态来更新窗体上的状态窗口 </summary>
public void UpdateStatus(string msg)
{
if (txtStatus.InvokeRequired)
{
BeginInvoke(new UpdateStatusDelegate(UpdateStatus), msg);
}
else
{
if (!msg.EndsWith("\r\n"))
msg += "\r\n";
txtStatus.Text += msg;
txtStatus.SelectionStart = txtStatus.Text.Length;
txtStatus.ScrollToCaret();//将控件内容滚动到当前插入符号位置
}
}
private delegate void GameOverDelegate();
/// <summary>此方法通过从“工作流实例 ID”组合框中删除已完成的工作流实例 ID 来更新窗体 </summary>
private void GameOver()
{
if (this.InvokeRequired)
{
BeginInvoke(new GameOverDelegate(GameOver));
}
else
{
cboInstanceId.Items.Remove(cboInstanceId.SelectedItem);
cboInstanceId.SelectedItem = null;
}
}
private void btnQuit_Click(object sender, EventArgs e)
{
//Application.EnableVisualStyles();
//Application.Run(new UnLoadWorkflowHostForm());
this.Close();
}
private void EnterGuess_Click(object sender, EventArgs e)
{
if (this.WorkflowInstanceId == Guid.Empty)
{
MessageBox.Show("Please select a workflow.");
return;
}
int guess;
if (!Int32.TryParse(txtGuess.Text, out guess))
{
MessageBox.Show("Please enter an integer.");
txtGuess.SelectAll();
txtGuess.Focus();
return;
}
//从存储区提取出之前保存的指定Id的工作流实例
WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(this.WorkflowInstanceId, store);
Activity wf = new StateMachineNumberGuessWorkflow();
//这里不用提供输入参数了
WorkflowApplication wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity);
ConfigureWorkflowApplication(wfApp);
wfApp.Load(instance);
wfApp.ResumeBookmark("EnterGuess", guess);
txtGuess.Clear();
txtGuess.Focus();
}
}
}
创建和运行长时间运行的工作流(一)
创建和运行长时间运行的工作流(二)
创建和运行长时间运行的工作流(三)
源代码