分析PersistableIdleAction的枚举值
我们在创建和运行长时间运行的工作流(二)中的ConfigureWorkflowApplication方法里面,在wfApp的PersistableIdle事件发生后,返回PersistableIdleAction.Unload,目的是指定 System.Activities.WorkflowApplication 应保持并卸载工作流,意为宿主不再锁定工作流实例,这样我们就可以创建新的工作流宿主wfApp2去加载此工作流实例了。
但是如果我们将PersistableIdleAction.Unload改成PersistableIdleAction.Persist,那么这个时候工作流实例是被宿主锁定的。我们可以通过下面的截图看到两者的不同:
PersistableIdleAction.Unload方式
PersistableIdleAction.Persist方式
我们可以看到[InstancesTable]表中的前后两条记录在SurrogateLockOwner字段上的变化,后者被锁定。如果我们现在开始输入数字,点击EnterGuess按钮后会出现以下画面,提示:InstancePersistenceCommand 的执行被中断,因为另一个有效的 InstanceHandle 在实例“e4e87df9-8166-444d-ad94-03d8549feefe”上持有锁,这表示已加载该实例的未过时副本。应使用或卸载所加载的实例副本及其关联 InstanceHandle。
这个错误提示告诉我们,要么让原宿主在新宿主加载工作流实例前卸载此工作流实例(即第一种方式);要么用原宿主加载工作流实例。
如果我们用原宿主WorkflowApplication加载工作流实例,则代码实现如下:
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 PersistWorkflowHostForm : Form { const string connectionString = "Server=.;Initial Catalog=WF45GettingStartedTutorial;integrated security=true"; //const string connectionString = "Server=Server11;Initial Catalog=WorkflowInstanceStore;uid=sa;pwd=sa"; SqlWorkflowInstanceStore store; bool workflowStarting; Dictionary<Guid, WorkflowApplication> wfApps = new Dictionary<Guid, WorkflowApplication>(); /// <summary> 当前工作流实例Id </summary> public Guid WorkflowInstanceId { get { return this.cboInstanceId.SelectedIndex == -1 ? Guid.Empty : (Guid)cboInstanceId.SelectedItem; } } public PersistWorkflowHostForm() { InitializeComponent(); } private void PersistWorkflowHostForm_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) { //让原始宿主卸载工作流实例,否则WorkflowApplication.GetInstance会无法进行 if (wfApps.ContainsKey(WorkflowInstanceId)) wfApps[this.WorkflowInstanceId].Unload(); //WorkflowApplicationInstance指定有关工作流应用程序实例的信息。 WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(this.WorkflowInstanceId, store); lblVersion.Text = instance.InstanceId.ToString(); //使状态机转换到放弃状态。由于调用GetInstance方法后,该工作流应用程序实例将被工作流宿主锁定。 //所以需要调用Abandon方法做一个类似解锁的操作。 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); wfApps.Add(wfApp.Id, wfApp); //由于工作流实例还未保存到存储区(数据库),所以当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>(); //由于Extensions中的StringWriter会不断在原值基础上追加新值,所以直接输入的话,显示的内容会不断重复 string sign = "-=-=-"; foreach (var writer in writers) { string msg = writer.ToString(); if (msg.IndexOf(sign) != -1) msg = msg.Substring(msg.LastIndexOf(sign) + sign.Length); UpdateStatus(msg); writer.Write(sign); //writer.Flush(); } // 摘要:enum PersistableIdleAction 指定在允许持久性的情况下,当工作流进入空闲状态时发生的操作。 // None = 0, 指定不执行任何操作。 // Unload = 1, 指定 System.Activities.WorkflowApplication 应保持并卸载工作流。 // Persist = 2 指定 System.Activities.WorkflowApplication 应保持工作流。 return PersistableIdleAction.Persist; }; } 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 { wfApps.Remove(Guid.Parse(cboInstanceId.SelectedItem.ToString())); cboInstanceId.Items.Remove(cboInstanceId.SelectedItem); cboInstanceId.SelectedItem = null; } } private void btnQuit_Click(object sender, EventArgs e) { 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; } //利用原宿主卸载工作流实例 if (wfApps.ContainsKey(this.WorkflowInstanceId)) { wfApps[this.WorkflowInstanceId].Unload(); wfApps.Remove(this.WorkflowInstanceId); } //从存储区提取出之前保存的指定Id的工作流实例 WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(this.WorkflowInstanceId, store); Activity wf = new StateMachineNumberGuessWorkflow(); WorkflowApplication wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity); ConfigureWorkflowApplication(wfApp); //由于读取了“Id”属性,因此已为 WorkflowApplication 生成一个实例 ID。生成 ID 之后,不能使用 WorkflowApplication 加载实例。 //下面两行代码的顺序不能颠倒 wfApp.Load(instance); wfApps.Add(wfApp.Id, wfApp); wfApp.ResumeBookmark("EnterGuess", guess); txtGuess.Clear(); txtGuess.Focus(); } } }建议运行方式:
1、先选择最大值为100,然后创建新Game1(此时可以开始猜测值),猜过数轮后;
2、再选择最大值为10,重新创建一个Game2
3、在下拉框中选择Game1对应的实例Id,重新加载Game1,进行猜值,最后成功时,你会看到你成功所需的轮数是两次猜测过程之和,这就说明了我们是从原来的实例加载数据。
创建和运行长时间运行的工作流(一)
创建和运行长时间运行的工作流(二)
创建和运行长时间运行的工作流(三)
源代码