创建和运行长时间运行的工作流(三)

分析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,进行猜值,最后成功时,你会看到你成功所需的轮数是两次猜测过程之和,这就说明了我们是从原来的实例加载数据。


创建和运行长时间运行的工作流(一)

创建和运行长时间运行的工作流(二)

创建和运行长时间运行的工作流(三)

源代码


你可能感兴趣的:(持久化,wf,persistent,Unload)