使用 WorkflowInvoker 和 WorkflowApplication

Windows Workflow Foundation (WF) 提供了几种托管工作流的方法。 WorkflowInvoker 提供了一种简单的方法来调用工作流,就像它是方法调用一样,并且只能用于不使用持久性的工作流。 WorkflowApplication 提供了一个更丰富的模型,用于执行工作流,其中包括生命周期事件通知、执行控件、书签恢复和持久性。 WorkflowServiceHost 提供对消息传递活动的支持,主要用于工作流服务。 本主题介绍如何使用 WorkflowInvokerWorkflowApplication 托管工作流。 有关使用 WorkflowServiceHost 托管工作流的详细信息,请参阅工作流服务和托管工作流服务概述

使用 WorkflowInvoker

WorkflowInvoker 提供用于执行工作流的模型,就好像它是方法调用一样。 若要使用 WorkflowInvoker调用工作流,请调用 Invoke 该方法并传入要调用的工作流的工作流定义。 在本示例中,使用 WriteLine 调用 WorkflowInvoker 活动。

Activity wf = new WriteLine
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

使用 WorkflowInvoker 调用工作流时,该工作流在调用线程上执行,并且在该工作流完成之前(包括任何空闲时间)会阻止 Invoke 方法。 若要配置一个工作流必须在其间完成的超时间隔,请使用一个采用 Invoke 参数的 TimeSpan 重载。 在本示例中,将使用两个不同的超时间隔来调用两次工作流。 第一个工作流完成,但第二个工作流不会完成。

Activity wf = new Sequence()
{
    Activities =
    {
        new WriteLine()
        {
            Text = "Before the 1 minute delay."
        },
        new Delay()
        {
            Duration = TimeSpan.FromMinutes(1)
        },
        new WriteLine()
        {
            Text = "After the 1 minute delay."
        }
    }
};

// This workflow completes successfully.
WorkflowInvoker.Invoke(wf, TimeSpan.FromMinutes(2));

// This workflow does not complete and a TimeoutException
// is thrown.
try
{
    WorkflowInvoker.Invoke(wf, TimeSpan.FromSeconds(30));
}
catch (TimeoutException ex)
{
    Console.WriteLine(ex.Message);
}

注释

仅在达到超时间隔且工作流在执行期间进入空闲状态时才会引发 TimeoutException。 如果工作流未进入空闲状态,那么完成时间超过指定超时间隔的工作流将会成功完成。

WorkflowInvoker 还提供调用方法的异步版本。 有关详细信息,请参阅 InvokeAsyncBeginInvoke

设置工作流的输入参数

使用输入参数字典可将数据传入工作流,其中自变量名作为键,并映射到工作流的输入自变量。 在此示例中,调用 a WriteLine 并使用输入参数字典指定其 Text 参数的值。

Activity wf = new WriteLine();

Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Text", "Hello World.");

WorkflowInvoker.Invoke(wf, inputs);

检索工作流的输出参数

可以使用从调用 Invoke返回的输出字典获取工作流的输出参数。 以下示例调用一个工作流,该工作流包含一个具有两个输入参数和两个 Divide 输出参数的活动。 调用工作流时,arguments 字典将被传递,其中包含每个输入参数的值,并按照参数名称进行索引。 当对 Invoke 的调用返回时,将在 outputs 字典中返回每个输出参数(也由参数名键控)。

public sealed class Divide : CodeActivity
{
    [RequiredArgument]
    public InArgument<int> Dividend { get; set; }

    [RequiredArgument]
    public InArgument<int> Divisor { get; set; }

    public OutArgument<int> Remainder { get; set; }
    public OutArgument<int> Result { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        int quotient = Dividend.Get(context) / Divisor.Get(context);
        int remainder = Dividend.Get(context) % Divisor.Get(context);

        Result.Set(context, quotient);
        Remainder.Set(context, remainder);
    }
}
int dividend = 500;
int divisor = 36;

Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("Dividend", dividend);
arguments.Add("Divisor", divisor);

IDictionary<string, object> outputs =
    WorkflowInvoker.Invoke(new Divide(), arguments);

Console.WriteLine($"{dividend} / {divisor} = {outputs["Result"]} Remainder {outputs["Remainder"]}");

如果工作流源自ActivityWithResult,比如CodeActivity<TResult>Activity<TResult>,并且除了已经定义好的Result输出参数外还有其他输出参数,则必须使用特定重载Invoke来检索这些额外的参数。 为此,传入的 Invoke 工作流定义的类型必须为类型 Activity。 在此示例中,Divide 活动派生自 CodeActivity<int>,但被声明为 Activity,以便于使用 Invoke 的非泛型重载,该重载返回参数字典而不是单个返回值。

public sealed class Divide : CodeActivity<int>
{
    public InArgument<int> Dividend { get; set; }
    public InArgument<int> Divisor { get; set; }
    public OutArgument<int> Remainder { get; set; }

    protected override int Execute(CodeActivityContext context)
    {
        int quotient = Dividend.Get(context) / Divisor.Get(context);
        int remainder = Dividend.Get(context) % Divisor.Get(context);

        Remainder.Set(context, remainder);

        return quotient;
    }
}
int dividend = 500;
int divisor = 36;

Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("Dividend", dividend);
arguments.Add("Divisor", divisor);

Activity wf = new Divide();

IDictionary<string, object> outputs =
    WorkflowInvoker.Invoke(wf, arguments);

Console.WriteLine($"{dividend} / {divisor} = {outputs["Result"]} Remainder {outputs["Remainder"]}");

使用 WorkflowApplication

WorkflowApplication 为工作流实例管理提供了一组丰富的功能。 WorkflowApplication 充当实际 WorkflowInstance线程安全代理,它封装运行时,并提供创建和加载工作流实例、暂停和恢复、终止和生命周期事件的通知的方法。 要使用 WorkflowApplication 运行工作流,首先创建 WorkflowApplication,订阅任何所需的生命周期事件,启动工作流,然后等待其完成。 在此示例中,创建由活动组成的 WriteLine 工作流定义,并使用指定的工作流定义创建一个 WorkflowApplication 工作流定义。 Completed 已被处理,以便在工作流完成时通知主机,工作流通过调用 Run 启动,随后主机等待工作流完成。 工作流完成后,将设置 AutoResetEvent,主机应用程序可以恢复执行,如以下示例所示。

AutoResetEvent syncEvent = new AutoResetEvent(false);

Activity wf = new WriteLine
{
    Text = "Hello World."
};

// Create the WorkflowApplication using the desired
// workflow definition.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Handle the desired lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    syncEvent.Set();
};

// Start the workflow.
wfApp.Run();

// Wait for Completed to arrive and signal that
// the workflow is complete.
syncEvent.WaitOne();

WorkflowApplication 生命周期事件

此外,当工作流卸载(Completed)、中止(Unloaded)、变得空闲(AbortedIdle)或发生未经处理的异常(PersistableIdle)时,主机作者可以收到通知OnUnhandledException。 工作流应用程序开发人员可以处理这些通知并采取适当的作,如以下示例所示。

wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Terminated.");
        Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
    }
    else
    {
        Console.WriteLine($"Workflow {e.InstanceId} Completed.");

        // Outputs can be retrieved from the Outputs dictionary,
        // keyed by argument name.
        // Console.WriteLine($"The winner is {e.Outputs["Winner"]}.");
    }
};

wfApp.Aborted = delegate (WorkflowApplicationAbortedEventArgs e)
{
    // Display the exception that caused the workflow
    // to abort.
    Console.WriteLine($"Workflow {e.InstanceId} Aborted.");
    Console.WriteLine($"Exception: {e.Reason.GetType().FullName}\n{e.Reason.Message}");
};

wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
    // Perform any processing that should occur
    // when a workflow goes idle. If the workflow can persist,
    // both Idle and PersistableIdle are called in that order.
    Console.WriteLine($"Workflow {e.InstanceId} Idle.");
};

wfApp.PersistableIdle = delegate (WorkflowApplicationIdleEventArgs e)
{
    // Instruct the runtime to persist and unload the workflow.
    // Choices are None, Persist, and Unload.
    return PersistableIdleAction.Unload;
};

wfApp.Unloaded = delegate (WorkflowApplicationEventArgs e)
{
    Console.WriteLine($"Workflow {e.InstanceId} Unloaded.");
};

wfApp.OnUnhandledException = delegate (WorkflowApplicationUnhandledExceptionEventArgs e)
{
    // Display the unhandled exception.
    Console.WriteLine($"OnUnhandledException in Workflow {e.InstanceId}\n{e.UnhandledException.Message}");

    Console.WriteLine($"ExceptionSource: {e.ExceptionSource.DisplayName} - {e.ExceptionSourceInstanceId}");

    // Instruct the runtime to terminate the workflow.
    // Other choices are Abort and Cancel. Terminate
    // is the default if no OnUnhandledException handler
    // is present.
    return UnhandledExceptionAction.Terminate;
};

设置工作流的输入参数

数据在启动工作流时可以通过参数字典传入,类似于使用WorkflowInvoker时传入数据的方式。 字典中的每个项都映射到指定工作流的输入参数。 在此示例中,调用包含 WriteLine 活动的工作流,并使用输入参数字典指定其 Text 参数。

AutoResetEvent syncEvent = new AutoResetEvent(false);

Activity wf = new WriteLine();

// Create the dictionary of input parameters.
Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Text", "Hello World!");

// Create the WorkflowApplication using the desired
// workflow definition and dictionary of input parameters.
WorkflowApplication wfApp = new WorkflowApplication(wf, inputs);

// Handle the desired lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    syncEvent.Set();
};

// Start the workflow.
wfApp.Run();

// Wait for Completed to arrive and signal that
// the workflow is complete.
syncEvent.WaitOne();

检索工作流的输出参数

工作流完成后,可以通过访问Completed字典在WorkflowApplicationCompletedEventArgs.Outputs处理程序中检索任何输出参数。 以下示例使用 WorkflowApplication 承载工作流。 WorkflowApplication实例是使用由单个DiceRoll活动组成的工作流定义构造的。 活动 DiceRoll 具有两个输出参数,这些参数表示掷骰子操作的结果。 工作流完成后,输出将在 Completed 处理程序中被检索。

public sealed class DiceRoll : CodeActivity
{
    public OutArgument<int> D1 { get; set; }
    public OutArgument<int> D2 { get; set; }

    static Random r = new Random();

    protected override void Execute(CodeActivityContext context)
    {
        D1.Set(context, r.Next(1, 7));
        D2.Set(context, r.Next(1, 7));
    }
}
// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(new DiceRoll());

// Subscribe to any desired workflow lifecycle events.
wfApp.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
{
    if (e.CompletionState == ActivityInstanceState.Faulted)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Terminated.");
        Console.WriteLine($"Exception: {e.TerminationException.GetType().FullName}\n{e.TerminationException.Message}");
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine($"Workflow {e.InstanceId} Canceled.");
    }
    else
    {
        Console.WriteLine($"Workflow {e.InstanceId} Completed.");

        // Outputs can be retrieved from the Outputs dictionary,
        // keyed by argument name.
        Console.WriteLine($"The two dice are {e.Outputs["D1"]} and {e.Outputs["D2"]}.");
    }
};

// Run the workflow.
wfApp.Run();

注释

WorkflowApplicationWorkflowInvoker 接受输入参数的字典并返回包含out参数的字典。 这些字典的参数、属性和返回值是IDictionary<string, object>类型。 传入的字典类的实际实例可以是实现 IDictionary<string, object>的任何类。 在这些示例中,使用了Dictionary<string, object>。 有关字典的详细信息,请参阅 IDictionary<TKey,TValue>Dictionary<TKey,TValue>

使用书签将数据传递到正在运行的工作流

书签是活动可以被动等待恢复的机制,是将数据传递到正在运行的工作流实例的机制。 如果某个活动正在等待数据,它可以创建一个Bookmark并注册一个在Bookmark恢复时调用的回调方法,如以下示例所示。

public sealed class ReadLine : NativeActivity<string>
{
    [RequiredArgument]
    public InArgument<string> BookmarkName { get; set; }

    protected override void Execute(NativeActivityContext context)
    {
        // Create a Bookmark and wait for it to be resumed.
        context.CreateBookmark(BookmarkName.Get(context),
            new BookmarkCallback(OnResumeBookmark));
    }

    // NativeActivity derived activities that do asynchronous operations by calling
    // one of the CreateBookmark overloads defined on System.Activities.NativeActivityContext
    // must override the CanInduceIdle property and return true.
    protected override bool CanInduceIdle
    {
        get { return true; }
    }

    public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj)
    {
        // When the Bookmark is resumed, assign its value to
        // the Result argument.
        Result.Set(context, (string)obj);
    }

执行时,ReadLine 活动创建 Bookmark,注册回调,然后等待 Bookmark 继续。 恢复时,ReadLine活动会将通过Bookmark传递的数据分配给其Result参数。 在此示例中,将创建一个工作流,该 ReadLine 工作流使用活动收集用户名并将其显示在控制台窗口中。

Variable<string> name = new Variable<string>();

Activity wf = new Sequence
{
    Variables = { name },
    Activities =
     {
         new WriteLine
         {
             Text = "What is your name?"
         },
         new ReadLine
         {
             BookmarkName = "UserName",
             Result = new OutArgument<string>(name)
         },
         new WriteLine
         {
             Text = new InArgument<string>((env) =>
                 ("Hello, " + name.Get(env)))
         }
     }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);

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

// Run the workflow.
wfApp.Run();

// Wait for the workflow to go idle before gathering
// the user's input.
idleEvent.WaitOne();

// Gather the user's input and resume the bookmark.
// Bookmark resumption only occurs when the workflow
// is idle. If a call to ResumeBookmark is made and the workflow
// is not idle, ResumeBookmark blocks until the workflow becomes
// idle before resuming the bookmark.
BookmarkResumptionResult result = wfApp.ResumeBookmark("UserName",
    Console.ReadLine());

// Possible BookmarkResumptionResult values:
// Success, NotFound, or NotReady
Console.WriteLine($"BookmarkResumptionResult: {result}");

执行 ReadLine 活动时,该活动创建一个名为 BookmarkUserName,然后等待书签继续。 宿主收集所需的数据,然后继续 Bookmark。 工作流恢复后,会显示名称,然后完成。

主机应用程序可以检查工作流,以确定是否有任何活动书签。 它可以通过调用 GetBookmarks 实例的 WorkflowApplication 方法,或检查 WorkflowApplicationIdleEventArgs 处理程序中的 Idle 来完成此操作。

下面的代码示例与前面的示例类似,只是在恢复书签之前枚举了活动书签。 工作流启动后,当 Bookmark 被创建并且工作流进入空闲状态时,GetBookmarks 将被调用。 工作流完成后,以下输出会显示到控制台。

你叫什么名字?
BookmarkName: UserName - OwnerDisplayName: ReadLineSteveHello, Steve

Variable<string> name = new Variable<string>();

Activity wf = new Sequence
{
    Variables = { name },
    Activities =
     {
         new WriteLine
         {
             Text = "What is your name?"
         },
         new ReadLine
         {
             BookmarkName = "UserName",
             Result = new OutArgument<string>(name)
         },
         new WriteLine
         {
             Text = new InArgument<string>((env) =>
                 ("Hello, " + name.Get(env)))
         }
     }
};

// Create a WorkflowApplication instance.
WorkflowApplication wfApp = new WorkflowApplication(wf);

// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);

wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
    // You can also inspect the bookmarks from the Idle handler
    // using e.Bookmarks

    idleEvent.Set();
};

// Run the workflow.
wfApp.Run();

// Wait for the workflow to go idle and give it a chance
// to create the Bookmark.
idleEvent.WaitOne();

// Inspect the bookmarks
foreach (BookmarkInfo info in wfApp.GetBookmarks())
{
    Console.WriteLine($"BookmarkName: {info.BookmarkName} - OwnerDisplayName: {info.OwnerDisplayName}");
}

// Gather the user's input and resume the bookmark.
wfApp.ResumeBookmark("UserName", Console.ReadLine());

下面的代码示例检查传递给 WorkflowApplicationIdleEventArgs 实例的 Idle 处理程序的 WorkflowApplication。 在此示例中,处于空闲状态的工作流有一个Bookmark,其名称为EnterGuess,由名为ReadInt的活动拥有。 此代码示例基于 作方法:运行工作流,该工作流是 入门教程的一部分。 Idle如果修改该步骤中的处理程序以包含此示例中的代码,则会显示以下输出。

BookmarkName: EnterGuess - OwnerDisplayName: ReadInt

wfApp.Idle = delegate (WorkflowApplicationIdleEventArgs e)
{
    foreach (BookmarkInfo info in e.Bookmarks)
    {
        Console.WriteLine($"BookmarkName: {info.BookmarkName} - OwnerDisplayName: {info.OwnerDisplayName}");
    }

    idleEvent.Set();
};

概要

WorkflowInvoker 提供一种轻量的方法来调用工作流,虽然它允许在工作流开始时传入数据和从已完成的工作流中提取数据,但对于更复杂的场景,它不提供支持,这时可以使用 WorkflowApplication