如何建立及執行長時間執行的工作流程

Windows Workflow Foundation (WF) 的其中一項核心功能,就是執行階段能夠將閒置的工作流程保存及卸載至資料庫。 操作說明:執行工作流程中的步驟示範使用主控台應用程式裝載工作流程的基本概念。 範例包括啟動工作流程、工作流程開發週期處理常式,以及繼續使用書籤。 為有效示範工作流程持續性,必須要有較複雜的工作流程主機,以支援啟動與繼續使用多個工作流程執行個體。 教學課程中的這個步驟,示範如何建立 Windows 表單主應用程式,以支援啟動與繼續使用多個工作流程執行個體、工作流程持續性,並且為後續教學課程步驟中示範的追蹤和版本設定等進階功能提供基礎。

注意

此教學課程步驟和後續步驟會使用操作說明:建立工作流程中所有的三種工作流程型別。

若要建立持續性資料庫

  1. 開啟 SQL Server Management Studio 並連線至本機伺服器,例如 .\SQLEXPRESS。 以滑鼠右鍵按一下本機伺服器的 [資料庫] 節點,然後選取 [新增資料庫]。 將新資料庫命名為 WF45GettingStartedTutorial,接受所有其他值,然後選取 [確定]

    注意

    建立資料庫之前,請確定您有本機伺服器的建立資料庫權限。

  2. 從 [檔案] 功能表中選擇 [開啟]、[檔案]。 瀏覽至下列資料夾:C:\Windows\Microsoft.NET\Framework\v4.0.30319\sql\en

    選取下列兩個檔案,然後按一下 [開啟]

    • SqlWorkflowInstanceStoreLogic.sql

    • SqlWorkflowInstanceStoreSchema.sql

  3. 從 [視窗] 功能表中選擇 SqlWorkflowInstanceStoreSchema.sql。 請務必選取 [可用的資料庫] 下拉式清單中的 [WF45GettingStartedTutorial],然後從 [查詢] 功能表中選擇 [執行]

  4. 從 [視窗] 功能表中選擇 SqlWorkflowInstanceStoreLogic.sql。 請務必選取 [可用的資料庫] 下拉式清單中的 [WF45GettingStartedTutorial],然後從 [查詢] 功能表中選擇 [執行]

    警告

    務必按照正確順序執行前面的兩個步驟。 如果未按照正確順序執行查詢,會發生錯誤,而且也無法正確地設定持續性資料庫。

將參考加入至 DurableInstancing 組件

  1. 以滑鼠右鍵按一下方案總管中的 NumberGuessWorkflowHost,並選取 [新增參考]

  2. 從 [新增參考] 清單中選取 [組件],並在 [搜尋組件] 方塊中輸入 DurableInstancing。 如此會篩選組件,讓您更容易選取所需的參考。

  3. 從 [搜尋結果] 清單中勾選 System.Activities.DurableInstancingSystem.Runtime.DurableInstancing 旁的核取方塊,然後按一下 [確定]

建立工作流程主表單

  1. 方案總管中,以滑鼠右鍵按一下 NumberGuessWorkflowHost,然後選擇 [新增]、[新增項目]

  2. 在 [已安裝] 範本清單中選擇 [Windows 表單],在 [名稱] 方塊中輸入 WorkflowHostForm,然後按一下 [新增]

  3. 設定表單中的下列屬性。

    屬性
    FormBorderStyle FixedSingle
    MaximizeBox False
    大小 400, 420
  4. 依指定順序將下列控制項加入到表單中,並依指示設定屬性。

    控制 屬性:值
    按鈕 名稱: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

    文字:工作流程執行個體識別碼
    ComboBox 名稱:InstanceId

    DropDownStyle:DropDownList

    位置:121、17

    大小:227、21
    標籤 位置:7、47

    文字:猜謎
    TextBox 名稱:Guess

    位置:50、44

    大小:65、20
    按鈕 名稱:EnterGuess

    位置:121、42

    大小:75、23

    文字:輸入猜測
    按鈕 名稱:QuitGame

    位置:274、42

    大小:75、23

    文字:退出
    TextBox 名稱:WorkflowStatus

    位置:10、73

    Multiline:True

    ReadOnly:True

    ScrollBars:垂直

    大小:338、208
  5. 將表單的 [AcceptButton] 屬性設定為 [EnterGuess]

下列範例示範完成的表單。

Screenshot of a Windows Workflow Foundation Workflow Host Form.

加入表單的屬性和 Helper 方法

本節中的步驟會將設定表單 UI 的屬性和 Helper 方法加入到表單類別中,以支援執行及繼續使用數字猜測工作流程。

  1. 以滑鼠右鍵按一下 [方案總管] 中的 [WorkflowHostForm],然後選擇 [檢視程式碼]

  2. 將下列 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;
    
  3. 將下列成員宣告新增至 [WorkflowHostForm] 類別。

    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 以參考您的資料庫。

  4. 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;
        }
    }
    

    InstanceId 下拉式方塊會顯示持續性工作流程執行個體識別碼的清單,且 WorkflowInstanceId 屬性會傳回目前選取的工作流程。

  5. 加入表單 Load 事件的處理常式。 若要新增處理常式,請切換至表單的 [設計檢視],按一下 [屬性] 視窗最上方的 [事件] 圖示,然後按兩下 [載入]

    Private Sub WorkflowHostForm_Load(sender As Object, e As EventArgs) Handles Me.Load
    
    End Sub
    
    private void WorkflowHostForm_Load(object sender, EventArgs e)
    {
    
    }
    
  6. 將下列程式碼新增至 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 下拉式方塊。

  7. 加入 SelectedIndexChangedInstanceId 處理常式。 若要新增處理常式,請切換至表單的 [設計檢視],選取 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)
    {
    
    }
    
  8. 將下列程式碼新增至 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();
    }
    
  9. 將下列 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 會在執行個體存放區中查詢持續性工作流成執行個體,並將執行個體識別碼加入 cboInstanceId 下拉式方塊。

  10. 將下列 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();
        }
    }
    
  11. 將下列 GameOver 方法及對應的委派加入至表單類別。 當工作流程完成時,此方法會從 [InstanceId] 下拉式方塊中移除已完成之工作流程的執行個體識別碼,以更新表單 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;
        }
    }
    

設定執行個體存放區、工作流程開發週期處理常式及擴充

  1. ConfigureWorkflowApplication 方法加入至表單類別。

    Private Sub ConfigureWorkflowApplication(wfApp As WorkflowApplication)
    
    End Sub
    
    private void ConfigureWorkflowApplication(WorkflowApplication wfApp)
    {
    }
    

    此方法會設定 WorkflowApplication、加入所需的擴充,然後加入工作流程開發週期事件的處理常式。

  2. ConfigureWorkflowApplication 中指定 SqlWorkflowInstanceStoreWorkflowApplication

    ' Configure the persistence store.
    wfApp.InstanceStore = store
    
    // Configure the persistence store.
    wfApp.InstanceStore = store;
    
  3. 接下來,建立 StringWriter 執行個體,並將其加入到 ExtensionsWorkflowApplication 集合中。 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);
    
  4. 加入 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();
    };
    
  5. 加入下列 AbortedOnUnhandledException 處理常式。 不會從 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;
    };
    
  6. 加入下列 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 列舉有三個值:NonePersistUnloadPersist 會使工作流程繼續持續,但不會導致工作流程卸載。 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 和版本控制

  1. 方案總管中,以滑鼠右鍵按一下 NumberGuessWorkflowHost,然後選擇 [新增]、[類別]。 在 [名稱] 方塊中鍵入 WorkflowVersionMap,然後按一下 [新增]

  2. 將下列 usingImports 陳述式加入至檔案最上方的其他 usingImports 陳述式。

    Imports System.Activities
    Imports NumberGuessWorkflowActivities
    
    using System.Activities;
    using NumberGuessWorkflowActivities;
    
  3. 用下列宣告取代 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 包含三個工作流程識別,其對應於此教學課程中的三個工作流程定義,在下列章節中,啟動及繼續使用工作流程時會使用這些識別。

啟動新的工作流程

  1. 加入 ClickNewGame 處理常式。 若要新增處理常式,請切換至表單的 [設計檢視],然後按兩下 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)
    {
    
    }
    
  2. 將下列程式碼加入至 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));
    
  3. 接下來,加入下列啟動工作流程的程式碼。 會使用 WorkflowIdentity Helper 類別,擷取對應至所選工作流程型別的 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);
    
  4. 接下來,加入下列程式碼,此程式碼會將工作流程加入到工作流程清單,並在表單上顯示該工作流程的版本資訊。

    ' 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;
    
  5. 呼叫 ConfigureWorkflowApplication 以設定執行個體存放區、擴充,以及此 WorkflowApplication 執行個體的工作流程開發週期處理常式。

    ' Configure the instance store, extensions, and
    ' workflow lifecycle handlers.
    ConfigureWorkflowApplication(wfApp)
    
    // Configure the instance store, extensions, and
    // workflow lifecycle handlers.
    ConfigureWorkflowApplication(wfApp);
    
  6. 最後,請呼叫 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();
    }
    

繼續使用工作流程

  1. 加入 ClickEnterGuess 處理常式。 若要新增處理常式,請切換至表單的 [設計檢視],然後按兩下 EnterGuess。 每當使用者按一下此按鈕,就會繼續使用該工作流程。

    Private Sub EnterGuess_Click(sender As Object, e As EventArgs) Handles EnterGuess.Click
    
    End Sub
    
    private void EnterGuess_Click(object sender, EventArgs e)
    {
    
    }
    
  2. 加入下列程式碼,以確保已在工作流程清單中選取工作流程,且使用者的猜測是有效的。

    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;
    }
    
  3. 接下來,擷取持續性工作流程執行個體的 WorkflowApplicationInstanceWorkflowApplicationInstance 代表尚未與工作流程定義相關聯的持續性工作流程執行個體。 DefinitionIdentityWorkflowApplicationInstance 包含持續性工作流程執行個體的 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);
    
  4. 建立 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);
    
  5. 最後,清除猜測文字方塊,並準備表單以接受另一種猜測。

    ' 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();
    }
    

終止工作流程

  1. 加入 ClickQuitGame 處理常式。 若要新增處理常式,請切換至表單的 [設計檢視],然後按兩下 QuitGame。 每當使用者按一下此按鈕,就會終止目前選取的工作流程。

    Private Sub QuitGame_Click(sender As Object, e As EventArgs) Handles QuitGame.Click
    
    End Sub
    
    private void QuitGame_Click(object sender, EventArgs e)
    {
    
    }
    
  2. 將下列程式碼加入至 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.");
    

若要建置並執行應用程式

  1. 方案總管中按兩下 Program.cs (或 Module1.vb),以顯示程式碼。

  2. 將下列 using (或 Imports) 陳述式加入至檔案最上方的其他 using (或 Imports) 陳述式。

    Imports System.Windows.Forms
    
    using System.Windows.Forms;
    
  3. 透過操作說明:執行工作流程移除或取消註解現有的工作流程裝載程式碼,並將其取代為下列程式碼。

    Sub Main()
        Application.EnableVisualStyles()
        Application.Run(New WorkflowHostForm())
    End Sub
    
    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.Run(new WorkflowHostForm());
    }
    
  4. 方案總管中,以滑鼠右鍵按一下 NumberGuessWorkflowHost,然後選擇 [屬性]。 在 [應用程式] 索引標籤中,將 [輸出型別] 指定為 [Windows 應用程式]。 此步驟是選用性的,但如果不進行此步驟,除了表單外還會顯示主控台視窗。

  5. 按 Ctrl+Shift+B 建置應用程式。

  6. 確定已將 NumberGuessWorkflowHost 設定為啟動應用程式,然後按 Ctrl+F5 以啟動應用程式。

  7. 請選取猜謎遊戲的範圍和要啟動的工作流程型別,然後按一下 [新遊戲]。 在 [猜謎] 方塊中輸入猜測,然後按一下 [開始] 提交猜測。 請注意,WriteLine 活動的輸出會顯示在表單上。

  8. 啟動數個使用不同工作流程型別和數字範圍的工作流程、輸入猜測,並且從 [工作流程執行個體識別碼] 清單中選取以切換工作流程。

    請注意,當您切換到新的工作流程時,先前的猜測和工作流程的進度都不會顯示在狀態視窗中。 不顯示狀態的原因是未擷取狀態,也未儲存在任何位置。 在本教學課程的下一個步驟操作說明:建立自訂追蹤參與者中,您會建立儲存此資訊的自訂追蹤參與者。