使用 WorkflowInvoker 和 WorkflowApplication

本主题适用于 Windows Workflow Foundation 4。

Windows Workflow Foundation (WF) 提供承载工作流的若干方法。WorkflowInvoker 提供调用工作流的简单方法,就像方法调用一样,并且只可用于不使用持久性的工作流。WorkflowApplication 为执行工作流提供更丰富的模型,包括生命周期事件通知、执行控制、书签恢复和持久性。WorkflowServiceHost 为消息传递活动提供支持,主要用于工作流服务。本主题介绍使用 WorkflowInvokerWorkflowApplication 的工作流承载。有关使用 WorkflowServiceHost 承载工作流的更多信息,请参见工作流服务承载工作流服务

使用 WorkflowInvoker

WorkflowInvoker 提供一种执行工作流的模型,如同使用方法调用。若要使用 WorkflowInvoker 调用工作流,请调用 Invoke 方法,并传入要调用的工作流的工作流定义。在本示例中,使用 WorkflowInvoker 调用 WriteLine 活动。

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

WorkflowInvoker.Invoke(wf);

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

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);
}
Dd560894.note(zh-cn,VS.100).gif注意:
仅在达到超时间隔且工作流在执行期间进入空闲状态时才会引发 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();

检索工作流的输出实参

当工作流完成时,可通过访问 System.Activities.WorkflowApplicationCompletedEventArgs.Outputs 字典在 Completed 处理程序中检索任何输出实参。下面的示例使用 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();
Dd560894.note(zh-cn,VS.100).gif注意:
WorkflowApplicationWorkflowInvoker 采用输入实参字典,并返回 out 实参的字典。这些字典形参、属性及返回值的类型为 IDictionary<string, object>。传入的字典类的实际实例可以是实现了 IDictionary<string, object> 的任何类。在这些示例中使用了 Dictionary<string, object>有关字典的更多信息,请参见 IDictionaryDictionary

使用书签将数据传入运行中的工作流

书签是使活动能够被动等待继续的机制,也是将数据传入运行中的工作流实例的机制。如果某个活动在等待数据,它可以创建 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 活动时,该活动创建一个名为 UserNameBookmark,然后等待书签继续。宿主收集所需的数据,然后继续 Bookmark。工作流继续,显示该名称,然后完成。

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

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

What is your name?
BookmarkName: UserName - OwnerDisplayName: ReadLine
Steve
Hello, 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());

下面的代码示例检查传递给 WorkflowApplication 实例的 Idle 处理程序的 WorkflowApplicationIdleEventArgs。在此示例中,进入空闲状态的工作流包含一个由名为 ReadInt 的活动所拥有的名为 EnterGuessBookmark。此代码示例基于入门教程中的如何:运行工作流。如果修改了该阶段中的 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 时可用的更为复杂的方案。