如何创建和运行长时间运行的工作流
Windows Workflow Foundation (WF) 的一个主要功能就是运行时能够在数据库中持久保存和卸载空闲的工作流。 如何:运行工作流中的步骤演示了使用控制台应用程序进行工作流承载的基本知识。 示例演示了启动工作流、工作流生命周期处理程序和恢复书签。 为了有效演示工作流持久性,需要一个支持启动和恢复多个工作流实例的更为复杂的工作流主机。 教程中此步骤演示了如何创建 Windows 窗体主机应用程序,此 Windows 窗体主机应用程序支持启动和恢复多个工作流实例、工作流持久性,并为高级功能(如在后续教程步骤中演示的跟踪和版本控制)提供基础。
注意
教程中的这一个步骤和后续的步骤使用了如何:创建工作流中所有三个工作流类型。
创建持久性数据库
打开 SQL Server Management Studio 并连接到本地服务器,例如 .\SQLEXPRESS。 右键单击本地服务器上的“数据库”节点,然后选择“新建数据库”。 将新数据库命名为 WF45GettingStartedTutorial,接受所有其他值,然后选择“确定”。
注意
在创建数据库前,确保你在本地服务器上具有创建数据库权限。
从“文件”菜单中依次选择“打开”和“文件”。 浏览到以下文件夹:C:\Windows\Microsoft.NET\Framework\v4.0.30319\sql\en
选择以下两个文件,然后单击“打开”。
SqlWorkflowInstanceStoreLogic.sql
SqlWorkflowInstanceStoreSchema.sql
从“窗口”菜单选择“SqlWorkflowInstanceStoreSchema.sql”。 确保在“可用数据库”下拉列表中选择“WF45GettingStartedTutorial”,然后从“查询”菜单中选择“执行”。
从“窗口”菜单中选择“SqlWorkflowInstanceStoreLogic.sql”。 确保在“可用数据库”下拉列表中选择“WF45GettingStartedTutorial”,然后从“查询”菜单中选择“执行”。
警告
请务必按正确顺序执行前面两个步骤。 如果不按顺序执行查询,系统会发生错误,并且持久性数据库会配置不正确。
添加对 DurableInstancing 程序集的引用
在解决方案资源管理器中右键单击“NumberGuessWorkflowHost”,然后选择“添加引用”。
从“添加引用”列表中选择“程序集”,然后在“搜索程序集”框中键入
DurableInstancing
。 这将筛选程序集,使您更易于选择所需引用。从“搜索结果”列表中选中“System.Activities.DurableInstancing”和“System.Runtime.DurableInstancing”旁边的复选框,然后单击“确定”。
创建工作流主机窗体
右键单击解决方案资源管理器中的“NumberGuessWorkflowHost”,然后依次选择“添加”、“新建项目”。
在“已安装”模板列表中,选择“Windows 窗体”,在“名称”框中键入
WorkflowHostForm
,然后单击“添加”。在窗体上配置以下属性。
属性 值 FormBorderStyle FixedSingle MaximizeBox 错误 大小 400, 420 按照指定顺序将以下控件添加到窗体,并根据指示配置这些属性。
控制 属性:值 Button 名称:NewGame
位置:13、13
大小:75、23
文本:新游戏标签 位置:94、18
文本:猜测一个数字,从 1 到ComboBox 名称:NumberRange
DropDownStyle:DropDownList
项:10、100、1000
位置:228、12
大小:143、21标签 位置:13、43
文本:工作流类型ComboBox 名称:WorkflowType
DropDownStyle:DropDownList
项:StateMachineNumberGuessWorkflow、FlowchartNumberGuessWorkflow、SequentialNumberGuessWorkflow
位置:94、40
大小:277、21标签 名称:WorkflowVersion
位置:13、362
文本:工作流版本GroupBox 位置:13、67
大小:358、287
文本:游戏注意
添加以下控件时,将其放入 GroupBox 中。
控制 属性:值 标签 位置:7、20
文本:工作流实例 IDComboBox 名称:InstanceId
DropDownStyle:DropDownList
位置:121、17
大小:227、21标签 位置:7、47
文本:推测TextBox 名称:Guess
位置:50、44
大小:65、20Button 名称:EnterGuess
位置:121、42
大小:75、23
名称:输入推测Button 名称:QuitGame
位置:274、42
大小:75、23
文本:退出TextBox 名称:WorkflowStatus
位置:10、73
多行:True
只读:True
滚动条:垂直
大小:338、208将窗体的“AcceptButton”属性设置为“EnterGuess”。
以下示例展示了完成的窗体。
添加窗体的属性和帮助器方法
本节的步骤将属性和帮助器添加至窗体类,此窗体类将配置窗体的 UI,以支持运行和恢复数字猜测工作流。
在解决方案资源管理器中右键单击“WorkflowHostForm”,然后选择“查看代码”。
在包含其他
using
(或Imports
)语句的文件的顶部添加以下using
(或Imports
)语句。Imports System.Activities Imports System.Activities.DurableInstancing Imports System.Data.SqlClient Imports System.IO Imports System.Windows.Forms
using System.Activities; using System.Activities.DurableInstancing; using System.Data.SqlClient; using System.IO; using System.Windows.Forms;
将以下成员声明添加到“WorkflowHostForm”类中。
重要
Microsoft 建议使用最安全的可用身份验证流。 如果要连接到 Azure SQL,建议使用 Azure 资源的托管标识这种身份验证方法。
Const connectionString = "Server=.\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI" Dim store As SqlWorkflowInstanceStore Dim workflowStarting As Boolean
const string connectionString = "Server=.\\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI"; SqlWorkflowInstanceStore store; bool workflowStarting;
注意
如果您的连接字符串不同,请更新
connectionString
引用数据库。将
WorkflowInstanceId
属性添加到WorkflowFormHost
类中。Public ReadOnly Property WorkflowInstanceId() As Guid Get If InstanceId.SelectedIndex = -1 Then Return Guid.Empty Else Return New Guid(InstanceId.SelectedItem.ToString()) End If End Get End Property
public Guid WorkflowInstanceId { get { return InstanceId.SelectedIndex == -1 ? Guid.Empty : (Guid)InstanceId.SelectedItem; } }
组合框显示持久性工作流实例 ID 的列表, 属性则返回当前选择的工作流
InstanceId
WorkflowInstanceId
。为窗体
Load
事件添加处理程序。 要添加该处理程序,请切换到窗体的“设计视图”,单击“属性”窗口顶部的“事件”图标,然后双击“Load”。Private Sub WorkflowHostForm_Load(sender As Object, e As EventArgs) Handles Me.Load End Sub
private void WorkflowHostForm_Load(object sender, EventArgs e) { }
将以下代码添加到
WorkflowHostForm_Load
。' Initialize the store and configure it so that it can be used for ' multiple WorkflowApplication instances. store = New SqlWorkflowInstanceStore(connectionString) WorkflowApplication.CreateDefaultInstanceOwner(store, Nothing, WorkflowIdentityFilter.Any) ' Set default ComboBox selections. NumberRange.SelectedIndex = 0 WorkflowType.SelectedIndex = 0 ListPersistedWorkflows()
// Initialize the store and configure it so that it can be used for // multiple WorkflowApplication instances. store = new SqlWorkflowInstanceStore(connectionString); WorkflowApplication.CreateDefaultInstanceOwner(store, null, WorkflowIdentityFilter.Any); // Set default ComboBox selections. NumberRange.SelectedIndex = 0; WorkflowType.SelectedIndex = 0; ListPersistedWorkflows();
窗体加载时,将配置
SqlWorkflowInstanceStore
,范围和工作流类型组合框将设置为默认值,持久性工作流实例将添加到InstanceId
组合框。为
SelectedIndexChanged
添加InstanceId
处理程序。 要添加该处理程序,请切换到窗体的“设计视图”,选择InstanceId
组合框,单击“属性”窗口顶部的“事件”图标,然后双击“SelectedIndexChanged”。Private Sub InstanceId_SelectedIndexChanged(sender As Object, e As EventArgs) Handles InstanceId.SelectedIndexChanged End Sub
private void InstanceId_SelectedIndexChanged(object sender, EventArgs e) { }
将以下代码添加到
InstanceId_SelectedIndexChanged
。 只要用户使用组合框选择一个工作流,此处理程序就会更新状态窗口。If InstanceId.SelectedIndex = -1 Then Return End If ' Clear the status window. WorkflowStatus.Clear() ' Get the workflow version and display it. ' If the workflow is just starting then this info will not ' be available in the persistence store so do not try and retrieve it. If Not workflowStarting Then Dim instance As WorkflowApplicationInstance = _ WorkflowApplication.GetInstance(WorkflowInstanceId, store) WorkflowVersion.Text = _ WorkflowVersionMap.GetIdentityDescription(instance.DefinitionIdentity) ' Unload the instance. instance.Abandon() End If
if (InstanceId.SelectedIndex == -1) { return; } // Clear the status window. WorkflowStatus.Clear(); // Get the workflow version and display it. // If the workflow is just starting then this info will not // be available in the persistence store so do not try and retrieve it. if (!workflowStarting) { WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(this.WorkflowInstanceId, store); WorkflowVersion.Text = WorkflowVersionMap.GetIdentityDescription(instance.DefinitionIdentity); // Unload the instance. instance.Abandon(); }
将下面的
ListPersistedWorkflows
方法添加到窗体类中。Private Sub ListPersistedWorkflows() Using localCon As New SqlConnection(connectionString) Dim localCmd As String = _ "SELECT [InstanceId] FROM [System.Activities.DurableInstancing].[Instances] ORDER BY [CreationTime]" Dim cmd As SqlCommand = localCon.CreateCommand() cmd.CommandText = localCmd localCon.Open() Using reader As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection) While (reader.Read()) ' Get the InstanceId of the persisted Workflow. Dim id As Guid = Guid.Parse(reader(0).ToString()) InstanceId.Items.Add(id) End While End Using End Using End Sub
using (var localCon = new SqlConnection(connectionString)) { string localCmd = "SELECT [InstanceId] FROM [System.Activities.DurableInstancing].[Instances] ORDER BY [CreationTime]"; SqlCommand cmd = localCon.CreateCommand(); cmd.CommandText = localCmd; localCon.Open(); using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { while (reader.Read()) { // Get the InstanceId of the persisted Workflow. Guid id = Guid.Parse(reader[0].ToString()); InstanceId.Items.Add(id); } } }
ListPersistedWorkflows
查询持久性工作流实例的实例存储,并将实例 ID 添加到cboInstanceId
组合框。将下面的
UpdateStatus
方法和对应的委托添加到窗体类中。 此方法使用当前正在运行的工作流的状态来更新窗体上的状态窗口。Private Delegate Sub UpdateStatusDelegate(msg As String) Public Sub UpdateStatus(msg As String) ' We may be on a different thread so we need to ' make this call using BeginInvoke. If InvokeRequired Then BeginInvoke(New UpdateStatusDelegate(AddressOf UpdateStatus), msg) Else If Not msg.EndsWith(vbCrLf) Then msg = msg & vbCrLf End If WorkflowStatus.AppendText(msg) ' Ensure that the newly added status is visible. WorkflowStatus.SelectionStart = WorkflowStatus.Text.Length WorkflowStatus.ScrollToCaret() End If End Sub
private delegate void UpdateStatusDelegate(string msg); public void UpdateStatus(string msg) { // We may be on a different thread so we need to // make this call using BeginInvoke. if (InvokeRequired) { BeginInvoke(new UpdateStatusDelegate(UpdateStatus), msg); } else { if (!msg.EndsWith("\r\n")) { msg += "\r\n"; } WorkflowStatus.AppendText(msg); WorkflowStatus.SelectionStart = WorkflowStatus.Text.Length; WorkflowStatus.ScrollToCaret(); } }
将下面的
GameOver
方法和对应的委托添加到窗体类中。 工作流完成时,此方法会从“InstanceId”组合框中删除已完成工作流的实例 ID,从而更新窗体 UI。Private Delegate Sub GameOverDelegate() Private Sub GameOver() If InvokeRequired Then BeginInvoke(New GameOverDelegate(AddressOf GameOver)) Else ' Remove this instance from the InstanceId combo box. InstanceId.Items.Remove(InstanceId.SelectedItem) InstanceId.SelectedIndex = -1 End If End Sub
private delegate void GameOverDelegate(); private void GameOver() { if (InvokeRequired) { BeginInvoke(new GameOverDelegate(GameOver)); } else { // Remove this instance from the combo box. InstanceId.Items.Remove(InstanceId.SelectedItem); InstanceId.SelectedIndex = -1; } }
配置实例存储、工作流生命周期处理程序和扩展
向窗体类添加
ConfigureWorkflowApplication
方法。Private Sub ConfigureWorkflowApplication(wfApp As WorkflowApplication) End Sub
private void ConfigureWorkflowApplication(WorkflowApplication wfApp) { }
此方法配置
WorkflowApplication
、添加所需扩展并添加工作流生命周期事件的处理程序。在
ConfigureWorkflowApplication
中,指定SqlWorkflowInstanceStore
的WorkflowApplication
。' Configure the persistence store. wfApp.InstanceStore = store
// Configure the persistence store. wfApp.InstanceStore = store;
接下来,创建一个
StringWriter
实例,并将它添加到Extensions
的WorkflowApplication
集合中。 当StringWriter
添加到扩展中时,它会捕获所有WriteLine
活动输出。 当工作流进入空闲状态时,WriteLine
输出可从StringWriter
提取并显示在窗体上。' Add a StringWriter to the extensions. This captures the output ' from the WriteLine activities so we can display it in the form. Dim sw As New StringWriter() wfApp.Extensions.Add(sw)
// Add a StringWriter to the extensions. This captures the output // from the WriteLine activities so we can display it in the form. var sw = new StringWriter(); wfApp.Extensions.Add(sw);
为
Completed
事件添加以下处理程序。 在工作流成功完成时,执行猜测数字的轮数会显示到状态窗口。 如果工作流终止,系统会显示导致终止的异常信息。 在处理程序结尾处调用了GameOver
方法,该方法从工作流列表中删除已完成的工作流。wfApp.Completed = _ Sub(e As WorkflowApplicationCompletedEventArgs) If e.CompletionState = ActivityInstanceState.Faulted Then UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}{vbCrLf}{e.TerminationException.Message}") ElseIf e.CompletionState = ActivityInstanceState.Canceled Then UpdateStatus("Workflow Canceled.") Else Dim turns As Integer = Convert.ToInt32(e.Outputs("Turns")) UpdateStatus($"Congratulations, you guessed the number in {turns} turns.") End If GameOver() End Sub
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e) { if (e.CompletionState == ActivityInstanceState.Faulted) { UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}\r\n{e.TerminationException.Message}"); } else if (e.CompletionState == ActivityInstanceState.Canceled) { UpdateStatus("Workflow Canceled."); } else { int turns = Convert.ToInt32(e.Outputs["Turns"]); UpdateStatus($"Congratulations, you guessed the number in {turns} turns."); } GameOver(); };
添加以下
Aborted
和OnUnhandledException
处理程序。GameOver
方法不从Aborted
处理程序中调用,因为在工作流实例中止时,它并未终止,且以后可能恢复实例。wfApp.Aborted = _ Sub(e As WorkflowApplicationAbortedEventArgs) UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}{vbCrLf}{e.Reason.Message}") End Sub wfApp.OnUnhandledException = _ Function(e As WorkflowApplicationUnhandledExceptionEventArgs) UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}{vbCrLf}{e.UnhandledException.Message}") GameOver() Return UnhandledExceptionAction.Terminate End Function
wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e) { UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}\r\n{e.Reason.Message}"); }; wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e) { UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}\r\n{e.UnhandledException.Message}"); GameOver(); return UnhandledExceptionAction.Terminate; };
添加以下
PersistableIdle
处理程序。 此处理程序检索添加的StringWriter
扩展,提取WriteLine
活动的输出,并将它显示在状态窗口中。wfApp.PersistableIdle = _ Function(e As WorkflowApplicationIdleEventArgs) ' Send the current WriteLine outputs to the status window. Dim writers = e.GetInstanceExtensions(Of StringWriter)() For Each writer In writers UpdateStatus(writer.ToString()) Next Return PersistableIdleAction.Unload End Function
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()); } return PersistableIdleAction.Unload; };
PersistableIdleAction 枚举有三个值:None、 Persist 和 Unload。 Persist 会导致工作流持久保存,但不会导致工作流卸载。 Unload 会导致工作流持久保存并卸载。
以下示例是完成的
ConfigureWorkflowApplication
方法。Private Sub ConfigureWorkflowApplication(wfApp As WorkflowApplication) ' Configure the persistence store. wfApp.InstanceStore = store ' Add a StringWriter to the extensions. This captures the output ' from the WriteLine activities so we can display it in the form. Dim sw As New StringWriter() wfApp.Extensions.Add(sw) wfApp.Completed = _ Sub(e As WorkflowApplicationCompletedEventArgs) If e.CompletionState = ActivityInstanceState.Faulted Then UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}{vbCrLf}{e.TerminationException.Message}") ElseIf e.CompletionState = ActivityInstanceState.Canceled Then UpdateStatus("Workflow Canceled.") Else Dim turns As Integer = Convert.ToInt32(e.Outputs("Turns")) UpdateStatus($"Congratulations, you guessed the number in {turns} turns.") End If GameOver() End Sub wfApp.Aborted = _ Sub(e As WorkflowApplicationAbortedEventArgs) UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}{vbCrLf}{e.Reason.Message}") End Sub wfApp.OnUnhandledException = _ Function(e As WorkflowApplicationUnhandledExceptionEventArgs) UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}{vbCrLf}{e.UnhandledException.Message}") GameOver() Return UnhandledExceptionAction.Terminate End Function wfApp.PersistableIdle = _ Function(e As WorkflowApplicationIdleEventArgs) ' Send the current WriteLine outputs to the status window. Dim writers = e.GetInstanceExtensions(Of StringWriter)() For Each writer In writers UpdateStatus(writer.ToString()) Next Return PersistableIdleAction.Unload End Function End Sub
private void ConfigureWorkflowApplication(WorkflowApplication wfApp) { // Configure the persistence store. wfApp.InstanceStore = store; // Add a StringWriter to the extensions. This captures the output // from the WriteLine activities so we can display it in the form. var sw = new StringWriter(); wfApp.Extensions.Add(sw); wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e) { if (e.CompletionState == ActivityInstanceState.Faulted) { UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}\r\n{e.TerminationException.Message}"); } else if (e.CompletionState == ActivityInstanceState.Canceled) { UpdateStatus("Workflow Canceled."); } else { int turns = Convert.ToInt32(e.Outputs["Turns"]); UpdateStatus($"Congratulations, you guessed the number in {turns} turns."); } GameOver(); }; wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e) { UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}\r\n{e.Reason.Message}"); }; wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e) { UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}\r\n{e.UnhandledException.Message}"); GameOver(); return UnhandledExceptionAction.Terminate; }; 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()); } return PersistableIdleAction.Unload; }; }
支持启动和恢复多个工作流类型
要恢复工作流实例,主机必须提供工作流定义。 在此教程中有三个工作流类型,且后续教程步骤会介绍这些类型的多个版本。 主机应用程序可通过 WorkflowIdentity
将标识信息与持久化工作流实例相关联。 本节中的步骤演示了如何创建一个实用工具类,以帮助将工作流标识从持久化工作流实例映射到对应的工作流定义。 有关 WorkflowIdentity
和版本控制的详细信息,请参阅使用 WorkflowIdentity 和版本控制。
在解决方案资源管理器中右键单击“NumberGuessWorkflowHost”,然后选择“添加”和“类”。 在“名称”框中键入
WorkflowVersionMap
,然后单击“确定”。在包含其他
using
或Imports
语句的文件的顶部添加以下using
或Imports
语句。Imports System.Activities Imports NumberGuessWorkflowActivities
using System.Activities; using NumberGuessWorkflowActivities;
使用以下声明替换
WorkflowVersionMap
类声明。Public Module WorkflowVersionMap Dim map As Dictionary(Of WorkflowIdentity, Activity) ' Current version identities. Public StateMachineNumberGuessIdentity As WorkflowIdentity Public FlowchartNumberGuessIdentity As WorkflowIdentity Public SequentialNumberGuessIdentity As WorkflowIdentity Sub New() map = New Dictionary(Of WorkflowIdentity, Activity) ' Add the current workflow version identities. StateMachineNumberGuessIdentity = New WorkflowIdentity With { .Name = "StateMachineNumberGuessWorkflow", .Version = New Version(1, 0, 0, 0) } FlowchartNumberGuessIdentity = New WorkflowIdentity With { .Name = "FlowchartNumberGuessWorkflow", .Version = New Version(1, 0, 0, 0) } SequentialNumberGuessIdentity = New WorkflowIdentity With { .Name = "SequentialNumberGuessWorkflow", .Version = New Version(1, 0, 0, 0) } map.Add(StateMachineNumberGuessIdentity, New StateMachineNumberGuessWorkflow()) map.Add(FlowchartNumberGuessIdentity, New FlowchartNumberGuessWorkflow()) map.Add(SequentialNumberGuessIdentity, New SequentialNumberGuessWorkflow()) End Sub Public Function GetWorkflowDefinition(identity As WorkflowIdentity) As Activity Return map(identity) End Function Public Function GetIdentityDescription(identity As WorkflowIdentity) As String Return identity.ToString() End Function End Module
public static class WorkflowVersionMap { static Dictionary<WorkflowIdentity, Activity> map; // Current version identities. static public WorkflowIdentity StateMachineNumberGuessIdentity; static public WorkflowIdentity FlowchartNumberGuessIdentity; static public WorkflowIdentity SequentialNumberGuessIdentity; static WorkflowVersionMap() { map = new Dictionary<WorkflowIdentity, Activity>(); // Add the current workflow version identities. StateMachineNumberGuessIdentity = new WorkflowIdentity { Name = "StateMachineNumberGuessWorkflow", Version = new Version(1, 0, 0, 0) }; FlowchartNumberGuessIdentity = new WorkflowIdentity { Name = "FlowchartNumberGuessWorkflow", Version = new Version(1, 0, 0, 0) }; SequentialNumberGuessIdentity = new WorkflowIdentity { Name = "SequentialNumberGuessWorkflow", Version = new Version(1, 0, 0, 0) }; map.Add(StateMachineNumberGuessIdentity, new StateMachineNumberGuessWorkflow()); map.Add(FlowchartNumberGuessIdentity, new FlowchartNumberGuessWorkflow()); map.Add(SequentialNumberGuessIdentity, new SequentialNumberGuessWorkflow()); } public static Activity GetWorkflowDefinition(WorkflowIdentity identity) { return map[identity]; } public static string GetIdentityDescription(WorkflowIdentity identity) { return identity.ToString(); } }
WorkflowVersionMap
包含与此教程中的三个工作流定义对应的三个工作流标识,并在以下各节中工作流启动和恢复时使用。
启动新工作流
为
Click
添加NewGame
处理程序。 要添加该处理程序,请切换到窗体的“设计视图”,然后双击NewGame
。 此时将添加NewGame_Click
处理程序,视图将切换为窗体的代码视图。 每当用户单击此按钮时,就会启动新工作流。Private Sub NewGame_Click(sender As Object, e As EventArgs) Handles NewGame.Click End Sub
private void NewGame_Click(object sender, EventArgs e) { }
将以下代码添加到 click 处理程序。 此代码创建工作流输入自变量的字典,以变量名称作为键。 此字典有一个条目,其中包含从范围组合框检索到的随机生成的数字范围。
Dim inputs As New Dictionary(Of String, Object)() inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem))
var inputs = new Dictionary<string, object>(); inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem));
接下来,添加以下用于启动工作流的代码。 对应于所选工作流类型的
WorkflowIdentity
和工作流定义通过WorkflowVersionMap
帮助器类进行检索。 接下来,将使用工作流定义、WorkflowApplication
和输入变量字典创建新的WorkflowIdentity
实例。Dim identity As WorkflowIdentity = Nothing Select Case WorkflowType.SelectedItem.ToString() Case "SequentialNumberGuessWorkflow" identity = WorkflowVersionMap.SequentialNumberGuessIdentity Case "StateMachineNumberGuessWorkflow" identity = WorkflowVersionMap.StateMachineNumberGuessIdentity Case "FlowchartNumberGuessWorkflow" identity = WorkflowVersionMap.FlowchartNumberGuessIdentity End Select Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(identity) Dim wfApp = New WorkflowApplication(wf, inputs, identity)
WorkflowIdentity identity = null; switch (WorkflowType.SelectedItem.ToString()) { case "SequentialNumberGuessWorkflow": identity = WorkflowVersionMap.SequentialNumberGuessIdentity; break; case "StateMachineNumberGuessWorkflow": identity = WorkflowVersionMap.StateMachineNumberGuessIdentity; break; case "FlowchartNumberGuessWorkflow": identity = WorkflowVersionMap.FlowchartNumberGuessIdentity; break; }; Activity wf = WorkflowVersionMap.GetWorkflowDefinition(identity); WorkflowApplication wfApp = new WorkflowApplication(wf, inputs, identity);
接下来,添加以下代码,此代码将工作流添加到工作流列表中,并在窗体上显示工作流的版本信息。
' Add the workflow to the list and display the version information. workflowStarting = True InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id) WorkflowVersion.Text = identity.ToString() workflowStarting = False
// Add the workflow to the list and display the version information. workflowStarting = true; InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id); WorkflowVersion.Text = identity.ToString(); workflowStarting = false;
调用
ConfigureWorkflowApplication
为此WorkflowApplication
实例配置实例存储、扩展以及工作流生命周期处理程序。' Configure the instance store, extensions, and ' workflow lifecycle handlers. ConfigureWorkflowApplication(wfApp)
// Configure the instance store, extensions, and // workflow lifecycle handlers. ConfigureWorkflowApplication(wfApp);
最后,调用
Run
。' Start the workflow. wfApp.Run()
// Start the workflow. wfApp.Run();
以下示例是完成的
NewGame_Click
处理程序。Private Sub NewGame_Click(sender As Object, e As EventArgs) Handles NewGame.Click ' Start a new workflow. Dim inputs As New Dictionary(Of String, Object)() inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem)) Dim identity As WorkflowIdentity = Nothing Select Case WorkflowType.SelectedItem.ToString() Case "SequentialNumberGuessWorkflow" identity = WorkflowVersionMap.SequentialNumberGuessIdentity Case "StateMachineNumberGuessWorkflow" identity = WorkflowVersionMap.StateMachineNumberGuessIdentity Case "FlowchartNumberGuessWorkflow" identity = WorkflowVersionMap.FlowchartNumberGuessIdentity End Select Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(identity) Dim wfApp = New WorkflowApplication(wf, inputs, identity) ' Add the workflow to the list and display the version information. workflowStarting = True InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id) WorkflowVersion.Text = identity.ToString() workflowStarting = False ' Configure the instance store, extensions, and ' workflow lifecycle handlers. ConfigureWorkflowApplication(wfApp) ' Start the workflow. wfApp.Run() End Sub
private void NewGame_Click(object sender, EventArgs e) { var inputs = new Dictionary<string, object>(); inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem)); WorkflowIdentity identity = null; switch (WorkflowType.SelectedItem.ToString()) { case "SequentialNumberGuessWorkflow": identity = WorkflowVersionMap.SequentialNumberGuessIdentity; break; case "StateMachineNumberGuessWorkflow": identity = WorkflowVersionMap.StateMachineNumberGuessIdentity; break; case "FlowchartNumberGuessWorkflow": identity = WorkflowVersionMap.FlowchartNumberGuessIdentity; break; }; Activity wf = WorkflowVersionMap.GetWorkflowDefinition(identity); var wfApp = new WorkflowApplication(wf, inputs, identity); // Add the workflow to the list and display the version information. workflowStarting = true; InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id); WorkflowVersion.Text = identity.ToString(); workflowStarting = false; // Configure the instance store, extensions, and // workflow lifecycle handlers. ConfigureWorkflowApplication(wfApp); // Start the workflow. wfApp.Run(); }
恢复工作流
为
Click
添加EnterGuess
处理程序。 要添加该处理程序,请切换到窗体的“设计视图”,然后双击EnterGuess
。 每当用户单击此按钮时,就会恢复工作流。Private Sub EnterGuess_Click(sender As Object, e As EventArgs) Handles EnterGuess.Click End Sub
private void EnterGuess_Click(object sender, EventArgs e) { }
添加以下代码,以确保在工作流列表中选择了一个工作流,且用户的猜测有效。
If WorkflowInstanceId = Guid.Empty Then MessageBox.Show("Please select a workflow.") Return End If Dim userGuess As Integer If Not Int32.TryParse(Guess.Text, userGuess) Then MessageBox.Show("Please enter an integer.") Guess.SelectAll() Guess.Focus() Return End If
if (WorkflowInstanceId == Guid.Empty) { MessageBox.Show("Please select a workflow."); return; } int guess; if (!Int32.TryParse(Guess.Text, out guess)) { MessageBox.Show("Please enter an integer."); Guess.SelectAll(); Guess.Focus(); return; }
接下来,检索持久化工作流实例的
WorkflowApplicationInstance
。WorkflowApplicationInstance
表示尚未与工作流定义关联的持久化工作流实例。DefinitionIdentity
的WorkflowApplicationInstance
包含持久化工作流实例的WorkflowIdentity
。 本教程中使用WorkflowVersionMap
实用工具类,将WorkflowIdentity
映射到正确的工作流定义。 检索工作流定义后,即使用正确的工作流定义创建WorkflowApplication
。Dim instance As WorkflowApplicationInstance = _ WorkflowApplication.GetInstance(WorkflowInstanceId, store) ' Use the persisted WorkflowIdentity to retrieve the correct workflow ' definition from the dictionary. Dim wf As Activity = _ WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity) ' Associate the WorkflowApplication with the correct definition Dim wfApp As New WorkflowApplication(wf, instance.DefinitionIdentity)
WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(WorkflowInstanceId, store); // Use the persisted WorkflowIdentity to retrieve the correct workflow // definition from the dictionary. Activity wf = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity); // Associate the WorkflowApplication with the correct definition var wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity);
创建
WorkflowApplication
后,请调用ConfigureWorkflowApplication
以配置实例存储、工作流生命周期处理程序以及扩展。 每次创建新WorkflowApplication
时都必须执行这些步骤,并且必须在将工作流实例加载到WorkflowApplication
中之前完成。 在加载工作流后,此工作流通过用户的猜测恢复执行。' Configure the extensions and lifecycle handlers. ' Do this before the instance is loaded. Once the instance is ' loaded it is too late to add extensions. ConfigureWorkflowApplication(wfApp) ' Load the workflow. wfApp.Load(instance) ' Resume the workflow. wfApp.ResumeBookmark("EnterGuess", userGuess)
// Configure the extensions and lifecycle handlers. // Do this before the instance is loaded. Once the instance is // loaded it is too late to add extensions. ConfigureWorkflowApplication(wfApp); // Load the workflow. wfApp.Load(instance); // Resume the workflow. wfApp.ResumeBookmark("EnterGuess", guess);
最后,清除猜测文本框,并使窗体准备接受其他猜测。
' Clear the Guess textbox. Guess.Clear() Guess.Focus()
// Clear the Guess textbox. Guess.Clear(); Guess.Focus();
以下示例是完成的
EnterGuess_Click
处理程序。Private Sub EnterGuess_Click(sender As Object, e As EventArgs) Handles EnterGuess.Click If WorkflowInstanceId = Guid.Empty Then MessageBox.Show("Please select a workflow.") Return End If Dim userGuess As Integer If Not Int32.TryParse(Guess.Text, userGuess) Then MessageBox.Show("Please enter an integer.") Guess.SelectAll() Guess.Focus() Return End If Dim instance As WorkflowApplicationInstance = _ WorkflowApplication.GetInstance(WorkflowInstanceId, store) ' Use the persisted WorkflowIdentity to retrieve the correct workflow ' definition from the dictionary. Dim wf As Activity = _ WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity) ' Associate the WorkflowApplication with the correct definition Dim wfApp As New WorkflowApplication(wf, instance.DefinitionIdentity) ' Configure the extensions and lifecycle handlers. ' Do this before the instance is loaded. Once the instance is ' loaded it is too late to add extensions. ConfigureWorkflowApplication(wfApp) ' Load the workflow. wfApp.Load(instance) ' Resume the workflow. wfApp.ResumeBookmark("EnterGuess", userGuess) ' Clear the Guess textbox. Guess.Clear() Guess.Focus() End Sub
private void EnterGuess_Click(object sender, EventArgs e) { if (WorkflowInstanceId == Guid.Empty) { MessageBox.Show("Please select a workflow."); return; } int guess; if (!Int32.TryParse(Guess.Text, out guess)) { MessageBox.Show("Please enter an integer."); Guess.SelectAll(); Guess.Focus(); return; } WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(WorkflowInstanceId, store); // Use the persisted WorkflowIdentity to retrieve the correct workflow // definition from the dictionary. Activity wf = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity); // Associate the WorkflowApplication with the correct definition var wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity); // Configure the extensions and lifecycle handlers. // Do this before the instance is loaded. Once the instance is // loaded it is too late to add extensions. ConfigureWorkflowApplication(wfApp); // Load the workflow. wfApp.Load(instance); // Resume the workflow. wfApp.ResumeBookmark("EnterGuess", guess); // Clear the Guess textbox. Guess.Clear(); Guess.Focus(); }
终止工作流
为
Click
添加QuitGame
处理程序。 要添加该处理程序,请切换到窗体的“设计视图”,然后双击QuitGame
。 每当用户单击此按钮,即终止当前选择的工作流。Private Sub QuitGame_Click(sender As Object, e As EventArgs) Handles QuitGame.Click End Sub
private void QuitGame_Click(object sender, EventArgs e) { }
将以下代码添加到
QuitGame_Click
处理程序。 此代码首先进行检查,以确保在工作流列表中选择了一个工作流。 然后,它将该持久化实例加载到WorkflowApplicationInstance
中,使用DefinitionIdentity
确定正确的工作流定义,然后初始化WorkflowApplication
。 接下来,通过调用ConfigureWorkflowApplication
来配置扩展和工作流生命周期处理程序。WorkflowApplication
配置好后,将加载它,然后调用Terminate
。If WorkflowInstanceId = Guid.Empty Then MessageBox.Show("Please select a workflow.") Return End If Dim instance As WorkflowApplicationInstance = _ WorkflowApplication.GetInstance(WorkflowInstanceId, store) ' Use the persisted WorkflowIdentity to retrieve the correct workflow ' definition from the dictionary. Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity) ' Associate the WorkflowApplication with the correct definition. Dim wfApp As New WorkflowApplication(wf, instance.DefinitionIdentity) ' Configure the extensions and lifecycle handlers. ConfigureWorkflowApplication(wfApp) ' Load the workflow. wfApp.Load(instance) ' Terminate the workflow. wfApp.Terminate("User resigns.")
if (WorkflowInstanceId == Guid.Empty) { MessageBox.Show("Please select a workflow."); return; } WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(WorkflowInstanceId, store); // Use the persisted WorkflowIdentity to retrieve the correct workflow // definition from the dictionary. Activity wf = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity); // Associate the WorkflowApplication with the correct definition var wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity); // Configure the extensions and lifecycle handlers ConfigureWorkflowApplication(wfApp); // Load the workflow. wfApp.Load(instance); // Terminate the workflow. wfApp.Terminate("User resigns.");
生成并运行应用程序
在“解决方案资源管理器”中双击“Program.cs”或“Module1.vb”,以显示其代码。
在包含其他
using
(或Imports
)语句的文件的顶部添加以下using
(或Imports
)语句。Imports System.Windows.Forms
using System.Windows.Forms;
删除或注释掉如何:运行工作流中现有的工作流承载代码,并替换为以下代码。
Sub Main() Application.EnableVisualStyles() Application.Run(New WorkflowHostForm()) End Sub
static void Main(string[] args) { Application.EnableVisualStyles(); Application.Run(new WorkflowHostForm()); }
在解决方案资源管理器中右键单击“NumberGuessWorkflowHost”,然后选择“属性”。 在“应用程序”选项卡中,为“输出类型”指定“Windows 应用程序”。 此步骤是可选的,但如果不执行此步骤,则除了窗体之外还会显示控制台窗口。
按 Ctrl+Shift+B 生成应用程序。
确保将“NumberGuessWorkflowHost”设置为启动应用程序,并按 Ctrl+F5 启动应用程序。
为猜数游戏选择一个范围,并选择要启动的工作流类型,然后单击“新游戏”。 在“推测”框中输入猜测值,然后单击“继续”提交该值。 请注意,
WriteLine
活动的输出将显示在窗体上。使用不同的工作流类型和数字范围启动多个工作流,输入一些猜测值,然后从“工作流实例 ID”列表中进行选择以便在工作流之间进行切换。
请注意,当切换到新工作流时,状态窗口中不会显示以前的猜测值和工作流进度。 状态不可用的原因是未将它捕获并保存在任何位置。 在教程的下一步,即如何:创建自定义跟踪参与者中,将创建保存此信息的自定义跟踪参与者。