使用 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

设置工作流的输入自变量

使用输入参数字典可将数据传入工作流,其中自变量名作为键,并映射到工作流的输入自变量。 在本示例中,将要调用 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("{0} / {1} = {2} Remainder {3}",
    dividend, divisor, outputs["Result"], 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("{0} / {1} = {2} Remainder {3}",
    dividend, divisor, outputs["Result"], 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)、中止工作流(Aborted)、工作流变空闲(IdlePersistableIdle)或产生未经处理的异常(OnUnhandledException)时,宿主作者会收到通知。 工作流应用程序开发人员可处理这些通知,并执行适当的操作,如下例所示。

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

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

wfApp.Aborted = delegate (WorkflowApplicationAbortedEventArgs e)
{
    // Display the exception that caused the workflow
    // to abort.
    Console.WriteLine("Workflow {0} Aborted.", e.InstanceId);
    Console.WriteLine("Exception: {0}\n{1}",
        e.Reason.GetType().FullName,
        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 {0} Idle.", e.InstanceId);
};

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 {0} Unloaded.", e.InstanceId);
};

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

    Console.WriteLine("ExceptionSource: {0} - {1}",
        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 承载一个工作流。 该示例使用包含单个 DiceRoll 活动的工作流定义构造一个 WorkflowApplication 实例。 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 {0} Terminated.", e.InstanceId);
        Console.WriteLine("Exception: {0}\n{1}",
            e.TerminationException.GetType().FullName,
            e.TerminationException.Message);
    }
    else if (e.CompletionState == ActivityInstanceState.Canceled)
    {
        Console.WriteLine("Workflow {0} Canceled.", e.InstanceId);
    }
    else
    {
        Console.WriteLine("Workflow {0} Completed.", e.InstanceId);

        // Outputs can be retrieved from the Outputs dictionary,
        // keyed by argument name.
        Console.WriteLine("The two dice are {0} and {1}.",
            e.Outputs["D1"], 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: {0}", result);

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

主机应用程序可以检查工作流,以确定其中是否存在任何活动书签。 这可以通过以下方式来完成:调用 GetBookmarks 实例的 WorkflowApplication 方法,或者,在 WorkflowApplicationIdleEventArgs 处理程序中检查 Idle

下面的代码示例与前一个示例类似,但在继续书签之前会枚举活动书签。 该示例启动工作流,一旦创建了 Bookmark 并且工作流进入空闲状态,就调用 GetBookmarks。 完成工作流时,会向控制台显示以下输出。

What is your name?
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: {0} - OwnerDisplayName: {1}",
        info.BookmarkName, info.OwnerDisplayName);
}

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

下面的代码示例检查传递给 WorkflowApplicationIdleEventArgs 实例的 Idle 处理程序的 WorkflowApplication。 在此示例中,进入空闲状态的工作流包含一个由名为 Bookmark 的活动所拥有的名为 EnterGuessReadInt。 此代码示例基于如何:运行工作流,该教程是入门教程的一部分。 如果修改了该阶段中的 Idle 处理程序以包含此示例中的代码,则将显示以下输出。

BookmarkName: EnterGuess - OwnerDisplayName: ReadInt

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

    idleEvent.Set();
};

总结

WorkflowInvoker 提供了一个简便的方法来调用工作流,尽管提供了在工作流开始时传入数据以及从完成的工作流中提取数据的方法,但不提供使用 WorkflowApplication 时可用的更为复杂的方案。