如何:运行工作流

本主题是 Windows Workflow Foundation 入门教程的延续,讨论如何创建工作流宿主并运行上一主题 How to: Create a Workflow 中定义的工作流。

注意

入门教程中的每个主题都依赖于前面的主题。 若要完成本主题,必须先完成 How to: Create an ActivityHow to: Create a Workflow

创建工作流宿主项目

  1. 使用 Visual Studio 2012 从上个主题如何:创建活动中打开解决方案。

  2. 解决方案资源管理器 中右键单击 WF45GettingStartedTutorial 解决方案,选择 “添加”“新建项目”

    提示

    如果未显示“解决方案资源管理器”窗口,请从“视图”菜单中选择“解决方案资源管理器”

  3. “已安装” 节点中,选择 “Visual C#”“工作流” (或 “Visual Basic”“工作流”)。

    注意

    Visual C#Visual Basic 节点可能位于“已安装”节点中的“其他语言”节点下,具体取决于哪种编程语言被配置为 Visual Studio 中的主语言。

    确保在 .NET Framework 版本下拉列表中选择“.NET Framework 4.5”。 从 “工作流” 列表中选择 “工作流控制台应用程序” 。 在“名称”框中键入 NumberGuessWorkflowHost,然后单击“确定”。 这将创建适合初学者的工作流应用程序,它具备基本的工作流承载支持。 基本承载代码将修改用于运行工作流应用程序。

  4. 解决方案资源管理器 中右键单击新添加的 NumberGuessWorkflowHost ,然后选择 “添加引用”。 在 “添加引用” 列表中选择 “解决方案” ,选中 NumberGuessWorkflowActivities旁边的复选框,然后单击 “确定”

  5. 解决方案资源管理器 中右键单击 Workflow1.xaml ,然后选择 “删除”。 单击“确定”以确认。

修改工作流承载代码

  1. 解决方案资源管理器 中双击 “Program.cs”“Module1.vb” ,以显示其代码。

    提示

    如果未显示“解决方案资源管理器”窗口,请从“视图”菜单中选择“解决方案资源管理器”

    因为此项目是用 “工作流控制台应用程序” 模板创建的,所以 “Program.cs”“Module1.vb” 包含以下基本工作流承载代码。

    ' Create and cache the workflow definition.
    Dim workflow1 As Activity = New Workflow1()
    WorkflowInvoker.Invoke(workflow1)
    
    // Create and cache the workflow definition.
    Activity workflow1 = new Workflow1();
    WorkflowInvoker.Invoke(workflow1);
    

    生成的承载代码使用 WorkflowInvokerWorkflowInvoker 提供一种简单工作流调用方法,就像方法调用一样,仅可用于不使用持久性的工作流。 WorkflowApplication 为执行工作流(包括生命周期事件通知、执行控制、书签恢复和持久性)提供更丰富的模型。 此示例使用书签并且将 WorkflowApplication 用于承载工作流。 在 using Program.cs using Module1.vb 顶部的现有 using Imports 语句下面,添加以下Imports 语句。

    Imports NumberGuessWorkflowActivities
    Imports System.Threading
    
    using NumberGuessWorkflowActivities;
    using System.Threading;
    

    用以下基本 WorkflowInvoker 承载代码替换使用 WorkflowApplication 的代码行。 此示例承载代码演示承载和调用工作流的基本步骤,但尚不包含用于成功运行本主题中的工作流的功能。 在以下步骤中,将修改此基本代码并添加其他功能,直到完成应用程序。

    注意

    你必须将以下示例中的 Workflow1 替换为 FlowchartNumberGuessWorkflowSequentialNumberGuessWorkflowStateMachineNumberGuessWorkflow(取决于在上一步骤如何:创建工作流中完成的工作流)。 如果不替换 Workflow1 ,在尝试生成或运行工作流时会出现生成错误。

    AutoResetEvent syncEvent = new AutoResetEvent(false);
    
    WorkflowApplication wfApp =
        new WorkflowApplication(_wf);
    
    wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
    {
        syncEvent.Set();
    };
    
    wfApp.Aborted = delegate (WorkflowApplicationAbortedEventArgs e)
    {
        Console.WriteLine(e.Reason);
        syncEvent.Set();
    };
    
    wfApp.OnUnhandledException = delegate (WorkflowApplicationUnhandledExceptionEventArgs e)
    {
        Console.WriteLine(e.UnhandledException.ToString());
        return UnhandledExceptionAction.Terminate;
    };
    
    wfApp.Run();
    
    syncEvent.WaitOne();
    
    Dim syncEvent As New AutoResetEvent(False)
    
    Dim wfApp As New WorkflowApplication(New Workflow1())
    
    wfApp.Completed =
        Sub(e As WorkflowApplicationCompletedEventArgs)
            syncEvent.Set()
        End Sub
    
    wfApp.Aborted =
        Sub(e As WorkflowApplicationAbortedEventArgs)
            Console.WriteLine(e.Reason)
            syncEvent.Set()
        End Sub
    
    wfApp.OnUnhandledException =
        Function(e As WorkflowApplicationUnhandledExceptionEventArgs)
            Console.WriteLine(e.UnhandledException)
            Return UnhandledExceptionAction.Terminate
        End Function
    
    wfApp.Run()
    
    syncEvent.WaitOne()
    

    此代码将创建一个 WorkflowApplication,订阅三个工作流生命周期事件,通过调用 Run来启动工作流,然后等待工作流完成。 工作流完成时,将设置 AutoResetEvent ,并且宿主应用程序也完成。

设置工作流的输入参数

  1. “Program.cs”“Module1.vb” 顶部的现有 usingImports 语句下面,添加以下语句。

  2. 将创建新 WorkflowApplication 的代码行替换为以下代码,该代码创建一个参数字典并在创建后将其传递给工作流。

    注意

    请将以下示例中的 Workflow1 替换为 FlowchartNumberGuessWorkflowSequentialNumberGuessWorkflow和 or StateMachineNumberGuessWorkflow和 depending on which workflow you completed in the previous Workflow1 中完成的工作流)。 如果不替换 Workflow1 ,在尝试生成或运行工作流时会出现生成错误。

    var inputs = new Dictionary<string, object>() { { "MaxNumber", 100 } };
    
    WorkflowApplication wfApp = new(_wf, inputs)
    {
    
    Dim inputs As New Dictionary(Of String, Object)
    inputs.Add("MaxNumber", 100)
    
    Dim wfApp As New WorkflowApplication(New Workflow1(), inputs)
    

    此字典包含一个键为 MaxNumber的元素。 输入字典中的键对应工作流根活动的输入参数。 工作流使用MaxNumber 来确定随机生成数的上边界。

检索工作流的输出参数

  1. 修改 Completed 处理程序以检索并显示工作流使用的轮数。

    Completed = delegate (WorkflowApplicationCompletedEventArgs e)
    {
        int Turns = Convert.ToInt32(e.Outputs["Turns"]);
        Console.WriteLine("Congratulations, you guessed the number in {0} turns.", Turns);
    
        syncEvent.Set();
    },
    
    wfApp.Completed =
        Sub(e As WorkflowApplicationCompletedEventArgs)
            Dim Turns As Integer = Convert.ToInt32(e.Outputs("Turns"))
            Console.WriteLine("Congratulations, you guessed the number in {0} turns.", Turns)
    
            syncEvent.Set()
        End Sub
    

继续执行书签

  1. 将以下代码添加到 Main 方法顶部,紧接在现有 AutoResetEvent 声明之后。

    AutoResetEvent idleEvent = new AutoResetEvent(false);
    
    Dim idleEvent As New AutoResetEvent(False)
    
  2. 将以下 Idle 处理程序添加到 Main中,紧接在现有的三个工作流生命周期处理程序的下面。

        Idle = delegate (WorkflowApplicationIdleEventArgs e)
        {
            idleEvent.Set();
        }
    };
    
    wfApp.Idle =
        Sub(e As WorkflowApplicationIdleEventArgs)
            idleEvent.Set()
        End Sub
    

    每次工作流变为空闲状态等待下一个猜测时,都会调用此处理程序并设置 idleAction AutoResetEvent。 下面步骤中的代码使用 idleEventsyncEvent 来确定工作流是在等待下一个猜测还是已完成。

    注意

    在本示例中,宿主应用程序在 CompletedIdle 处理程序中使用自动重置事件将宿主应用程序与工作流的进度同步。 在继续执行书签之前,不需要阻止并等待工作流变为空闲状态,但在此示例中需要同步事件,以使宿主知道工作流是否已完成,或是否在等待使用 Bookmark的更多用户输入。 有关详细信息,请参阅书签

  3. 移除对 WaitOne的调用,并替换为收集用户输入并恢复 Bookmark的代码。

    移除下面的代码行。

    syncEvent.WaitOne();
    
    syncEvent.WaitOne()
    

    使用下面的示例替换它。

    // Loop until the workflow completes.
    WaitHandle[] handles = new WaitHandle[] { syncEvent, idleEvent };
    while (WaitHandle.WaitAny(handles) != 0)
    {
        // Gather the user input and resume the bookmark.
        bool validEntry = false;
        while (!validEntry)
        {
            if (!Int32.TryParse(Console.ReadLine(), out int Guess))
            {
                Console.WriteLine("Please enter an integer.");
            }
            else
            {
                validEntry = true;
                wfApp.ResumeBookmark("EnterGuess", Guess);
            }
        }
    }
    
    ' Loop until the workflow completes.
    Dim waitHandles As WaitHandle() = New WaitHandle() {syncEvent, idleEvent}
    Do While WaitHandle.WaitAny(waitHandles) <> 0
        'Gather the user input and resume the bookmark.
        Dim validEntry As Boolean = False
        Do While validEntry = False
            Dim Guess As Integer
            If Int32.TryParse(Console.ReadLine(), Guess) = False Then
                Console.WriteLine("Please enter an integer.")
            Else
                validEntry = True
                wfApp.ResumeBookmark("EnterGuess", Guess)
            End If
        Loop
    Loop
    

生成并运行应用程序

  1. 解决方案资源管理器 中,右键单击 NumberGuessWorkflowHost 项目,然后选择 “设为启动项目”

  2. 按 Ctrl+F5 生成并运行应用程序。 尝试以尽可能少的次数猜出该数。

    要尝试其他工作流样式的应用程序,请用 Workflow1WorkflowApplicationFlowchartNumberGuessWorkflow(取决于所需工作流样式)替换代码中用于创建 SequentialNumberGuessWorkflowStateMachineNumberGuessWorkflow

    var inputs = new Dictionary<string, object>() { { "MaxNumber", 100 } };
    
    WorkflowApplication wfApp = new(_wf, inputs)
    {
    
    Dim inputs As New Dictionary(Of String, Object)
    inputs.Add("MaxNumber", 100)
    
    Dim wfApp As New WorkflowApplication(New Workflow1(), inputs)
    

    有关如何向工作流应用程序添加持久性的说明,请参阅下一主题 How to: Create and Run a Long Running Workflow

示例

下面的示例是 Main 方法的完整代码清单。

注意

请将以下示例中的 Workflow1 替换为 FlowchartNumberGuessWorkflowSequentialNumberGuessWorkflow和 or StateMachineNumberGuessWorkflow和 depending on which workflow you completed in the previous Workflow1 中完成的工作流)。 如果不替换 Workflow1 ,在尝试生成或运行工作流时会出现生成错误。

static void Main(string[] args)
{
    AutoResetEvent syncEvent = new AutoResetEvent(false);
    AutoResetEvent idleEvent = new AutoResetEvent(false);

    var inputs = new Dictionary<string, object>() { { "MaxNumber", 100 } };

    WorkflowApplication wfApp = new(_wf, inputs)
    {

        Completed = delegate (WorkflowApplicationCompletedEventArgs e)
        {
            int Turns = Convert.ToInt32(e.Outputs["Turns"]);
            Console.WriteLine("Congratulations, you guessed the number in {0} turns.", Turns);

            syncEvent.Set();
        },

        Aborted = delegate (WorkflowApplicationAbortedEventArgs e)
        {
            Console.WriteLine(e.Reason);
            syncEvent.Set();
        },

        OnUnhandledException = delegate (WorkflowApplicationUnhandledExceptionEventArgs e)
        {
            Console.WriteLine(e.UnhandledException.ToString());
            return UnhandledExceptionAction.Terminate;
        },

        Idle = delegate (WorkflowApplicationIdleEventArgs e)
        {
            idleEvent.Set();
        }
    };

    wfApp.Run();

    // Loop until the workflow completes.
    WaitHandle[] handles = new WaitHandle[] { syncEvent, idleEvent };
    while (WaitHandle.WaitAny(handles) != 0)
    {
        // Gather the user input and resume the bookmark.
        bool validEntry = false;
        while (!validEntry)
        {
            if (!Int32.TryParse(Console.ReadLine(), out int Guess))
            {
                Console.WriteLine("Please enter an integer.");
            }
            else
            {
                validEntry = true;
                wfApp.ResumeBookmark("EnterGuess", Guess);
            }
        }
    }
}
Sub Main()
    Dim syncEvent As New AutoResetEvent(False)
    Dim idleEvent As New AutoResetEvent(False)

    Dim inputs As New Dictionary(Of String, Object)
    inputs.Add("MaxNumber", 100)

    Dim wfApp As New WorkflowApplication(New Workflow1(), inputs)

    wfApp.Completed =
        Sub(e As WorkflowApplicationCompletedEventArgs)
            Dim Turns As Integer = Convert.ToInt32(e.Outputs("Turns"))
            Console.WriteLine("Congratulations, you guessed the number in {0} turns.", Turns)

            syncEvent.Set()
        End Sub

    wfApp.Aborted =
        Sub(e As WorkflowApplicationAbortedEventArgs)
            Console.WriteLine(e.Reason)
            syncEvent.Set()
        End Sub

    wfApp.OnUnhandledException =
        Function(e As WorkflowApplicationUnhandledExceptionEventArgs)
            Console.WriteLine(e.UnhandledException)
            Return UnhandledExceptionAction.Terminate
        End Function

    wfApp.Idle =
        Sub(e As WorkflowApplicationIdleEventArgs)
            idleEvent.Set()
        End Sub

    wfApp.Run()

    ' Loop until the workflow completes.
    Dim waitHandles As WaitHandle() = New WaitHandle() {syncEvent, idleEvent}
    Do While WaitHandle.WaitAny(waitHandles) <> 0
        'Gather the user input and resume the bookmark.
        Dim validEntry As Boolean = False
        Do While validEntry = False
            Dim Guess As Integer
            If Int32.TryParse(Console.ReadLine(), Guess) = False Then
                Console.WriteLine("Please enter an integer.")
            Else
                validEntry = True
                wfApp.ResumeBookmark("EnterGuess", Guess)
            End If
        Loop
    Loop
End Sub

另请参阅