基础
Windows 工作流设计模式
Matthew Milner
MSDN 代码库 中可用的代码下载
浏览在线代码
内容
执行工作的 N 数据项
侦听的超时
变体: 状态机
散点图收集
使用多个操作启动工作流服务
设计模式提供通用的、 可重复的方法来解决软件的开发任务,并很多不同的模式可以描述如何完成特定目标在代码中。 当开发人员开始工作与 Windows 流基础 (WF) 它们经常询问有关如何完成使用该技术的常见任务。 本月我将介绍几个在 WF 中使用的设计模式。
执行工作的 N 数据项
通常,工作流都不驱动完全由逻辑但还数据,(如组织中的用户的列表或订单,列表的一组工作流中的步骤需要执行一次的每个项目的。 尽管可能不是模式本身,该简单的、 可重用的位逻辑的是其他模式我在本文中讨论的一个重要组件。 此方案的关键使用复制活动循环访问的数据的集合和集合中执行相同的每一项活动。
复制活动提供驱动器迭代事件初始化每个数据项和条件,您可以打破执行子活动的数据项目的集合属性。 实质上是,复制活动提供 ForEach 结合 DoWhile 样式条件执行语句的语义。
渚嬪的方式 给定工作流与列表 <string> 类型的属性包含员工的电子邮件地址,可以循环访问列表和每个员工,发送消息中所示 图 1 .
图 1 复制 withSendMail 活动
鍦 ㄨ 繖绉嶆儏鍐典笅,复制活动必须 InitialChildData 属性绑定到一个集合实现 IEnumerable 接口包含用于电子邮件地址。 这些地址用于设置为每个迭代的收件人的地址。 通过处理 ChildInitialized 事件,您访问数据的项和执行动态活动实例。 图 2 显示了如何从集合中的电子邮件地址传递给事件,并可在相关的电子邮件活动实例上设置 RecipientAddress 属性。
正在初始化子活动的图 2
public List<string> emails = new List<string>
{"matt@contoso.com","msdnmag@example.com"};
private void InitChildSendMail(object sender, ReplicatorChildEventArgs e)
{
SendMailActivity sendMail = e.Activity as SendMailActivity;
sendMail.RecipientAddress = e.InstanceData.ToString();
}
按顺序或者以并行方式,可以执行复制活动。 顺序方式复制等待每个迭代完成后才能开始新的迭代。 并行方式所有活动是初始化一次计划和执行与非常类似于并行活动,但每个分支中相同的定义。 能够循环访问数据的项调用以并行方式,某些操作,等待响应的每一项是很多包括本文中讨论的几个的设计模式中的关键。
侦听的超时
在侦听超时方案,必须等待某些输入但只在一定的时间要求。 渚嬪的方式 您可能有通知使用电子邮件和需要等待但如果的答复,管理器管理器没有响应时间,在特定时间内您的工作流都应采取如发送提醒的进一步操作。
此模式的任何实现的核心是,Listen 活动。 侦听活动允许工作流可暂停,同时等待许多不同的事件或输入。 此功能还可以完成与并行活动,但不同之处等待所有的事件的并行活动而侦听活动可响应第一个事件发生并停止侦听的所有其他事件时。 将此功能能够等待一个指定由该延迟活动的时间与组合在一起使工作流等待一个事件,但超时,如果不会发生该事件。 图 3 显示了一个 Listen 活动等待消息到达通过 Windows 通信基础 (WCF) 或为过期超时。 请注意 Listen 活动可以有多个分支可以因此侦听对于很多不同的事件在同一时间。
图 3 侦听的多个分支的活动
此类实现使工作流等待一个特定的响应时间。 通常,在超时发生在工作流旨在采取相应的操作。 在超时发生后,展开管理器审核示例上,,经理应提醒她了她需要批准的未处理请求。 经理将提醒后,工作流需要还原到的响应超时时间,等待状态。 围绕一个侦听一个时活动使工作流继续等待直到满足某个条件。 侦听活动的分支内, 继续等待或收到的需要响应后上移动适当操作条件。 在简单的情况下一个标志可用于管理导致 While 的该条件循环,直到设置了标志的活动。 因此时,管理器发送响应,标志可设置和 While 活动关闭,允许工作流将移动到下一个活动。 与延迟活动分支中, 会发生延迟后,活动用于管理器发送提醒和确保仍将条件设置为强制 While 活动再次,安排子活动,如 图 4 所示。
图 4 收听与在发送提醒
在此的示例停止等待该触发器条件是只需在的经理发一个响应,但是,如果当然,复杂计算的任何级别可以接收对输入数据。 一个选项是将一个规则集,活动策略以确定是否已将移动到下一步工作流中满足所有条件。
变体: 状态机
您开发而不是顺序工作流的状态机工作流时发生了一个变体超时模式与侦听。 在这种情况下状态活动代替侦听活动并为侦听多个事件,在同一的时间包括使用延迟活动。 处于给定的状态说,等待供审批,您可以建立模型作为同一方案之前,等待响应或超时。 图 5 显示了示例工作流实现与之前相同的逻辑,但使用状态机工作流。
图 5 状态计算机侦听
它是实际易于管理以下条件,因为没有为时不需要活动。 而是,延迟时可以发送该提醒或执行其他操作,然后转换到当前的状态使用 SetState 活动这会导致延迟活动执行再次,重置超时。 如果接收到响应符合条件的继续,用于 SetState 活动将移动到下一状态。 这两个分支 图 6 所示。
图 6 侦听的一个状态的活动
散点图收集
您需要启动许多子工作流进行某些工作,使用散点图收集模式。 这些工作流可能所有执行相同的工作上不同的数据或每个可能执行不同的工作。 目标是启动的所有工作,优化的多个线程来完成的任务更快地如果如有的可能使用每个任务完成后要收集结果,然后通知父工作流。
可以启动多个工作流,只需通过复制活动和 InvokeWorkflow 活动。 工作流启动异步,即所需内容但它会等待父工作流中更具挑战性因为您需要一个可以从子流接收数据的阻止活动。 使用接收活动,父工作流可以等待完成从和接收任何结果返回每个工作流启动的每个子活动。 这种模式在父工作流中的高级视图如 图 7 所示。
图 7 复制与 InvokeWorkfl O 和接收活动
图使得此模式查看简单实现,但确保在子工作流可以正确回叫使用 WCF 父工作流所需的步骤的几个键。 需要从父工作流传递到每个要启用将数据发送回工作流实例标识符和以选择正确的接收活动会话标识符包括该父工作流子子上下文信息。 此外,父工作流必须寄宿作为 WCF 服务启用该的通信,但需要在开始使用 WorkflowRuntime,如 图 8 所示。
作为 WCF 服务启动工作流必须寄宿的图 8
WorkflowServiceHost host = new WorkflowServiceHost(typeof(MSDN.Workflows.
ParentWorkflow));
try
{
host.Open();
WorkflowRuntime runtime = host.Description.Behaviors.
Find<WorkflowRuntimeBehavior>().WorkflowRuntime;
WorkflowInstance instance = runtime.CreateWorkflow(
typeof(MSDN.Workflows.ParentWorkflow));
instance.Start();
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
finally
{
if (host != null && host.State == CommunicationState.Opened)
host.Close();
else
host.Abort();
}
每个子工作流参数输入至少需要父工作流 ID 和接收活动 ID 的除了需要处理子工作流中的任何业务数据。 该工作流的参数定义为在工作流定义的公共属性。
InvokeWorkflow 活动允许您将参数传递给子工作流,并提供属性对话框中的这些属性。 InvokeWorkflow 活动上的参数绑定到属性或工作流中的一个域。 但是,用于调用多个工作流复制活动参数需在代码中被设置,因为每次调用都需要唯一的值,; 可以使用当前的输入为每个的迭代设置属性或字段。 因此,InvokeWorkflow 活动上的参数应该绑定到,工作流中的字段,并且重创建子工作流之前,将在代码中更新这些字段。
您的初始斜率可能将属性设置在 ChildInitialized 事件过程中执行复制程序,为我前面,显示 SendMail 的示例,这是一个很好的位置开始。 但是时执行复制活动,在并行模式下,所有子级之前都初始化开始执行的任何实例。 因此,如果设置该属性在 ChildInitialized 事件中时活动执行该 InvokeWorkflow,活动的所有实例将都使用一组数据。 但是,ChildInitialized 事件不会提供对活动实例和推动迭代的数据项的访问。 一种方法是收集的数据项,以便它在执行过程中可到正确的活动实例相关的唯一标识符存储它。 图 9 显示 ChildInitialized 事件处理程序复制活动的实例数据键上唯一的标识符,ActivityExecutionContext 的字典中的存储位置。
图 9 在迭代过程中存储数据
private void InitChild(object sender, ReplicatorChildEventArgs e)
{
InvokeWorkflowActivity startWF =
(InvokeWorkflowActivity)e.Activity.GetActivityByName("StartChild
Workflow");
InputValueCollection[(Guid)e.Activity.GetValue(
Activity.ActivityContextGuidProperty)] = e.InstanceData.ToString();
}
下一步中,初始化 InvokeWorkflow 活动,您使用调用事件来设置参数。 此时执行,在子工作流的输入所需的所有值都是可用的。 工作流标识符可以被检索,WorkflowEnvironment,从,并可以从上下文属性在接收活动的实例上检索会话标识符。 最后,业务数据可以在检索对当前执行上下文使用标识符。 图 10 显示了代码以初始化参数被传递到工作流。
图 10 初始化 InvokeWorkflow 活动
private void PrepChildParams(object sender, EventArgs e)
{
InvokeWorkflowActivity startWf = sender as InvokeWorkflowActivity;
ReceiveActivity receive =
(ReceiveActivity)startWf.Parent.GetActivityByName(
"ReceiveChildCompletion");
Contracts.ChildWFRequest request = new Contracts.ChildWFRequest();
request.WFInstanceID = WorkflowEnvironment.WorkflowInstanceId.ToString();
request.ConversationID = receive.Context["conversationId"];
request.RequestValue =
InputValueCollection[(Guid)startWf.Parent.GetValue(
Activity.ActivityContextGuidProperty)];
StartWF_Input = request;
}
子工作流启动后,它就可以开始执行工作进行,并在完成时,使用发送活动通知父工作流。 在发送邮件前上下文必须将设置发送活动上,以确保邮件获取发送到正确的接收活动,父工作流中。 使用从父传递值,上下文正确使用可以设置该 BeforeSend 事件,如下所示。
e.SendActivity.Context = new Dictionary<string, string>{
{"instanceId", InputValues.WFInstanceID},
{"conversationId", InputValues.ConversationID}};
位置、 父工作流启动,和活动循环访问数据的集合执行复制程序中的所有这些部分,启动的每个项目的一个子工作流并等待从每个并行的消息。 然后,如子流完成,将它们发送一条消息回父可以继续处理后,所有子工作流已报告都了返回的结果。 使用此方法,子流可以运行在同一时间都有其自己提供真正的异步处理的线程。
使用多个操作启动工作流服务
在很多的示例工作流服务启动与一个单个的接收活动建模单个操作中。 在情况下我此处讨论您需要使用多个方法调用中启动工作流服务。 也就是客户端应用程序可能不总是调用相同的操作开始您的工作流进行交互,您需要能够设计工作流,以便可以启动多个操作的基础上。
有实际两个不同种类的这种模式,根据您要如何后第一次请求处理的请求。 第一个选项是启用工作流以启动多个操作之一,然后该操作完毕后上移动的工作流处理,直到您定义另一个操作可以被调用的点。 为此目的,您需要返回到侦听活动,并将其用作您的工作流定义中第一个活动。 中然后,活动的每个分支中, 添加一个接收活动、 配置它用适当的服务的操作信息并绑定处理,任何必要参数,如 图 11 所示。
图 11 多接收一个侦听活动中的活动
在关键步骤是确保侦听活动中的所有接收活动的都具有 CanCreateInstance 属性设置为 True。 这将指示 WCF 运行时如果没有上下文信息请求,该值指示一个现有的工作流实例上的可用,它是可以启动新实例根据配置的操作。 尽管它可能看上去接收以外的其他活动创建工作流略有奇数,运行库创建实例,然后启动,只将 WCF 消息的内容发送到接收活动的尝试。 在这种情况下将启动工作流后则这两种接收活动的执行并可以等待输入。
我提到过有两个变体,此模式。 当您使用侦听活动与前面的示例中相同的一个操作启动工作流,但然后将侦听活动完成之后完成一个分支,该服务不再能够接收对其他操作的请求侦听的建模。 这可能是完全是您希望一些的方案中,但在其他希望工作流之前移动处理整个组的操作。 也就是您知道工作流将接收该服务合同中的多个请求在不同的操作,但您不确定哪个请求将第一个。 在这种情况下而不是在侦听活动可以使用包含一个其 CanCreateInstance 属性设为 True 的接收活动的每个分支中使用并行活动。 这仍允许与任何的操作启动工作流但它还在工作流状态以接收所有其他操作调用各种分支中的建模。
最后,使用状态机工作流时, 可以更灵活地接收到一个特定的消息时,工作流的行为方式。 请考虑用初始状态包含多个事件驱动活动,每个接收活动作为起始活动,和每个接收标记为启用创建状态机。 正常情况下,将 State 活动的作用很像在侦听活动,但是作为开发人员,您决定从当前的状态移到另一个状态的控件时。 侦听活动完成关闭,,然后控件将移动到序列中下一个活动。 一个状态的活动与一个分支执行如果工作流没有移动到新的状态后工作流处于当前状态并继续等待已定义的输入。
若要用于语义 Listen 活动,如必须使用 SetState 活动调用的一个操作时,将工作流移动到下一状态。 这通常会使工作流进入在其中它正在等待要调用的不同 WCF 操作状态。 如果另一方面,要语义更接近但不是特定的顺序必须调用的所有操作的位置对并行模型,然后在每个接收活动后您可以选择不更改状态或恢复到相同的状态是 self-transition 使用转换 SetState 活动。
最后一个选项不是完全像并行活动模型一个潜在的重要方式。 与并行活动,已调用的操作后,它不能调用再次除非仿效并行活动的位置。 状态机器模型中,在工作流处于相同的状态,如果调用该操作后它可以接收消息的其他操作或原始操作。 在起的方式作用状态可以提供您语义类似于一个 Listen 活动在一个时活动,等待所有的事件,一个单独的事件响应和循环回然后等待这些相同的所有事件。
将您的问题和提出发送到 mmnet30@microsoft.com.
Matt Milner 是在技术人员在 Pluralsight,他连接的系统的技术 (WCF、 Windows 流、 BizTalk,"Dublin,"和 Azure 服务平台) 的焦点位置的成员。 Matt 也是一个独立的顾问特殊化 Microsoft.NET 应用程序设计和开发中。 Matt 定期共享本地、 地区,和国际会议,如技术来说他爱的技术。 Ed。 Microsoft 已作为 Matt 的周围连接的系统的技术其团体的贡献的 MVP。 联系人 Matt 通过他的博客: pluralsight.com/community/blogs/matt/.