基础知识

错误在工作流处理

Matt Milner

代码下载可从 MSDN 代码库
浏览代码联机

内容

处理工作流中的错误
处理宿主进程中的错误
处理自定义活动中的错误
使用补偿
重试活动

Windows Workflow Foundation (WF) 提供了定义丰富的业务流程和执行和管理这些进程在运行时环境的该工具。在任何业务过程中预期的执行流的例外情况发生,,并且开发人员需要能够编写可靠的应用程序逻辑来恢复这些例外情形。任何技术的大多数示例往往会忽略错误处理和正确恢复。在这个月的期,我您展示如何处理正确在 WF 编程模型的几个级别的异常以及如何生成异常处理功能为您的工作流和主机的格式。

处理工作流中的错误

开发人员构建业务流程必须能够处理业务情形,以确保进程本身具有复原性的异常,并且发生失败后可以继续。这一特别重要与工作流,它们通常定义长时间运行的进程以及在大多数的情况下了未处理的错误意味着不必重新启动进程。默认行为的工作流是如果异常发生在工作流,并且不处理,工作流将终止。因此,是工作流开发人员正确范围的工作、 处理错误,和内置工作流能够发生失败后重试工作非常关键。

处理工作流中的错误有很多事情与处理在 Microsoft.NET Framework 的目标代码和几个新概念的错误。若要处理错误,第一步是执行的定义一个范围。在.NET 代码中这完成了尝试关键字。在工作流中, 大多数的复合活动用于创建一个异常处理作用域。每个复合活动有主显示子活动的视图,而也有其他视图。图 1 中序列活动上下文菜单将显示如何在各种视图可以访问和选择查看错误处理程序选项的结果。

图 1 备用视图菜单和 SelectingView 错误处理程序

切换到错误处理程序视图时, 一个 FaultHandlers 活动被添加到序列活动的活动集合中。该 FaultHandlers 活动中可以添加单个 FaultHandler 活动。每个 FaultHandler 活动具有属性来定义错误类型,而且之类在.NET 中的一个 catch 表达式。

FaultHandler 活动是允许添加定义如何处理该异常的子活动的工作流开发人员的一个复合活动。这些活动可以提供要记录错误,请联系管理员或通常会采取处理在代码中的异常时的任何其他操作的功能。

FaultHandler 活动也具有包含在捕获该异常的错误属性。子活动可以绑定到此属性访问异常。图 2 ,在自定义的日志活动具有 FaultHandler 活动绑定到错误属性及其异常属性所示。日志活动可以立即写入异常信息记录 API、 Windows 事件日志、 Windows Management Instrumentation (WMI) 或所有其他目标。

图 2 绑定到错误

如 catch 块,FaultHandler 活动计算基于其错误类型。在定义工作流,FaultHandler 活动应添加到顺序 FaultHandlers 从最具体的错误,以在最不具体,从左到右。

图 3 执行继续在复合后

时发生异常,并在.NET 代码中捕获的 catch 块完成执行后尝试范围仍然存在。因此在一个的工作流执行继续下一个活动的处理该异常的复合活动后 (参见 图 3 )。

有两个重要概念 FaultHandler 活动是如何计算和执行。Throw 活动执行 (作为中 图 3 ),或其他活动会引发异常时, 运行库将置于 faulted 状态的活动,并计划执行活动 HandleFault 方法。我将进入如何活动实现此方法立即,但现在它是足以知道这是活动以清理的机会更多详细信息。

时该活动完成清理,将为已关闭状态,父活动被置于错误状态并清除任何子活动,并指示它是可以将移动到已关闭状态同样提供机会。它是到目前为止在复合活动信号是即可将移动到已关闭状态的则运行库检查状态并时, it is 错误,将检查 FaultHandlers 集合。如果 FaultHandler 活动找到具有匹配当前异常的错误类型,计划该 FaultHandler,并在停止计算。一旦将关闭该 FaultHandler 执行然后可以继续下一个活动。

异常发生时,运行库将尝试查找直接父复合活动的错误处理活动。如果没有匹配的处理程序发现的复合 faulted 和异常逐层达树中下一个的复合活动。这与类似如何气泡的.NET 异常向上堆栈调用的方法时未处理。如果该异常逐层一直找到根活动的工作流,并没有处理程序,然后终止工作流。

请注意工作流本身是一个复合活动,因此,可以具有定义为处理到达最高级别的异常的错误处理逻辑。这是最后一个机会工作流开发人员捕获并处理业务流程中的异常。

处理宿主进程中的错误

创建可靠的工作流重要时,具有可处理的异常的主机是您的应用程序的稳定性同样重要的。幸运的是,工作流运行时处理这些异常现成的强大并且 shields 主机进程从向上冒泡的大多数异常。

当异常工作流设置的气泡,通过在层次结构中, 发生,并且未捕获时,则工作流将终止,并在运行时上引发的事件。运行库的主机可以注册一个处理程序以便在这些异常发生,但例外情况不会导致主机故障时获得通知。若要以便获得通知有关这些终止,主机进程可以使用如下的代码从事件参数获取有关该异常的信息:

workflowRuntime.WorkflowTerminated += delegate(
  object sender, WorkflowTerminatedEventArgs e)
{
  Console.WriteLine(e.Exception.Message);
};

除了处理终止工作流,主机进程还能够获得通知运行库服务中发生的异常。是例如如果 SqlWorkflowPersistence­service 加载到运行库中,它将轮询数据库并可能尝试加载它们有更多工作来做定期工作流。试图加载工作流时, 持久性服务试图反序列化工作流,例如时,可能会引发异常。如果发生这种情况,值得再次主机进程不故障,这是这些服务不再次引发这些异常的原因。而是,他们引发到流运行时事件。运行库反过来引发一个 ServicesExceptionNotHandled 事件,可处理代码中,,如下所示:

workflowRuntime.ServicesExceptionNotHandled += delegate(
  object sender, ServicesExceptionNotHandledEventArgs snhe)
{
  Console.WriteLine(snhe.Exception.Message);
};

一般情况下的运行时服务的开发必须捕获有关异常是否为关键的异常时进行选择。SqlWorkflowPersistenceService 中, 无法加载一个工作流不意味着服务将无法工作。因此,有意义在这种情况下若要只引发事件允许主机进程,以确定是否进一步操作需要。但是,如果持久性服务无法连接到 SQL Server 数据库,然后它无法工作在。在这种情况下,而不是引发一个事件,它更有意义服务引发异常并将停止主机,以便可以解决此问题。

在开发自定义运行时服务时,建议的方法是从 WorkflowRuntimeService 文章类派生这些服务。此基的类提供同时访问运行库和受保护的方法引发 ServicesExceptionNotHandled 事件。异常发生在运行时服务执行时, 该服务应只引发该异常如果确实是一个不可恢复的错误。如果错误与一个工作流实例和不在常规执行该服务,然后事件应被引发相反。

处理自定义活动中的错误

为活动作者,异常处理执行稍有不同的含义。与异常处理活动中目的是双重: 处理它们时可能,防止向上冒泡和中断工作流和正确清理在未处理的异常逐层超出该活动的位置的情况下发生的异常。

因为活动是只是一个类,处理活动中的异常是不不同于任何其他类。如果可以使用 try/catch 块调用其他组件可能会引发错误。但是,一旦您的活动中捕获异常,您必须决定是要再次引发异常。如果异常为这不会影响该的活动或您的活动的结果有则更控制方法指示没有成功,这是首选的方法提供的反馈。如果但是,此异常意味着您的活动失败并不能完成其处理或宏正失败,然后则应引发异常,工作流开发人员可以设计业务流程来处理该异常。

在其他方面的处理活动中的异常处理清除活动资源。与不同的业务流程、 日志记录和通知, 集中错误处理的工作流中处理活动中的错误是主要侧重于清理活动执行中使用的资源。

处理错误的方式也取决是否要编写一个叶活动或一个复合活动。在一个叶活动 HandleFault 方法称为一个未处理的异常捕获由运行库以使该活动以释放可能正被使用并清除任何执行已开始的任何资源时。是例如如果该活动在执行过程中使用数据库,HandleFault 方法中则应确保以关闭该连接,如果不已关闭,并处理可能出现在使用中的任何其他资源。如果该活动已开始任何异步工作,这是时间来取消处理并释放用于该处理的资源。

为一个复合的活动 HandleFault 方法时,它可能因本身,活动中的逻辑错误,或者可能是因为子活动出错。在任何一的种情况下在对一个复合活动调用 HandleFault 方法目的是允许以清理的子活动活动。此清理涉及确保在复合不请求任何更多的活动在执行和取消正在执行的任何活动。幸运的是,在 composite­activity 基类中定义该 HandleFault 方法的在默认实现是复合活动调用 Cancel 方法。

取消是允许已开始部分的活动以异步方式工作和当前正在等待完成通知它们应取消的工作它们已启动并清除他们的资源,以便它们可以关闭该工作的其他机制。活动可免如果另一活动引发一个错误,或正常情况下如果控制流逻辑,父复合活动的决定取消工作。

活动是取消,运行库将将取消该活动的状态,并对活动调用 Cancel 方法。是例如 Replicator 活动可以启动另一个用于提供,数据的每条子活动的多个迭代,并计划并行运行这些活动。它还具有了 until­condition 属性,为每个子活动计算关闭。有可能,而在 until­condition 的评估会可能,以确定应完成该活动。

为了以关闭复制它必须首先关闭所有子活动。由于每个这些活动已计划,并且可能执行,Replicator 活动将检查 ExecutionStatus 属性的当前值,如果它 is 执行,发出请求运行库,若要取消该活动。

使用补偿

处理工作流中的错误使开发人员可以处理立即的例外条件。交易记录的使用也能够范围工作一起以确保一致性。但是,在长运行工作流,可能是两个工作单元需要一致性的但不能使用事务处理。

是例如工作流启动后它可能会更新可能添加到 CRM 系统的客户在业务线应用程序中数据。此工作可能甚至是在多个操作在 CRM 中与工作流的状态提供一致性交易记录的部分。然后,等待进一步输入一个用户可能需要天时间发生后,工作流将使用客户信息更新的会计系统。很重要的会计系统和 CRM 系统中都具有一致的数据,但不可以在这样的较大的时间范围的那些资源的使用原子事务。因此该问题,如何执行处理更新第二个系统,以确保一致性与已承诺在第一个系统的更改时发生的异常?

图 4 While 活动为重试逻辑

由于两个系统工作不是与交易记录保持一致,所需是一种检测更新第二个系统时,并提供返回,并撤消在初始的系统中应用的工作机会的错误或否则更改以确保一致性的机制。而检测此更改,并启动此过程的操作可以是自动,修复初始系统的工作显然必须由开发人员能指定。

在 WF 中这一过程称为补偿和多个活动用于帮助开发使用补偿的工作流。有关补偿以及如何使用在补偿的详细信息相关的活动请 MSDN Magazine 2007 年 6 月月刊中的事务性工作流上参阅 Dino Esposito 领先列 ("事务性工作流").

重试活动

工作流中的异常处理的问题之一是,异常时,即使如果您在捕获,执行上移到下一步进程中。很多的业务流程中执行真正应该不继续,直到成功执行该工作流中定义的业务逻辑。开发人员经常处理此通过一段提供重试逻辑并定义活动指出活动应继续执行,只要出现错误的条件的活动。进一步,延迟活动通常用于防止重试逻辑立即发生。

若要能够此重试模型您可以使用一个序列活动,作为一段的子活动。进一步,特定单位的序列中的工作通常包装以处理该的异常的另一个规则或复合活动中 acting 为错误处理范围与错误处理程序视图中定义的所有错误处理程序。然后 IfElse 活动常用于修改工作流到 while 上影响条件的状态活动。

在情况下没有异常出现的逻辑设置属性或标记某种类型的因此 While 活动可以关闭。如果未发生异常,然后该标志设置为导致 while 再次,执行的活动和一个延迟活动来进行下一次尝试之前暂停。图 4 显示一个示例使用在时重试工作流中的活动的活动。

当此特定模式工作在许多情况下时, 假设 5 或 10 个要重试的不同操作的工作流。快速将实现它是许多生成重试逻辑,为每个活动的工作。幸运的是,WF 使开发人员能够编写包括自定义复合活动的自定义活动。这意味着我可以编写自己的重试活动封装发生异常时再次执行子活动。为使此成为有价值,我想为用户提供两个密钥输入: 重试,和最大数目重试之前让异常气泡图上工作,并能处理的时间之间的延迟间隔。

在本专栏的其余部分,我将详细介绍重试活动中的逻辑。有关创建自定义活动的背景信息,请参阅我以前的文章 ("Windows 工作流: 生成自定义活动以扩展您的工作流的到达",有关使用该 activity­ExecutionContext 创建可迭代通过子活动的活动的详细信息,请参阅此列在 2007 年 6 月期"工作流中的 ActivityExecutionContext").

要正确地管理子活动,很重要能够监视活动,以了解何时发生错误。因此,执行子活动时, 重试活动不仅注册以便在子活动关闭,但也注册以便在子活动将被置于错误状态时获得通知时获得通知。图 5 显示用来启动子活动的每个迭代,BeginIteration 方法。计划活动之前, 将关闭和 Faulting 事件具有注册的处理程序。

图 5 执行子活动和注册的错误

Activity child = EnabledActivities[0];
ActivityExecutionContext newContext = 
  executionContext.ExecutionContextManager.CreateExecutionContext(child);

newContext.Activity.Closed += 
  new 
EventHandler<ActivityExecutionStatusChangedEventArgs>(child_Closed);

newContext.Activity.Faulting += 
  new 
EventHandler<ActivityExecutionStatusChangedEventArgs>(Activity_Faulting);

newContext.ExecuteActivity(newContext.Activity);

通常如果一个子活动错误父活动将也会使处于错误状态。 为了避免这的种情况下,当子的活动错误重试活动检查以查看是否活动已被重试最大次数。 如果重试次数已经已达到,此代码可为空出从而取消该异常该子活动,当前异常:

void Activity_Faulting(object sender, 
  ActivityExecutionStatusChangedEventArgs e)
{
  e.Activity.Faulting -= Activity_Faulting;
  if(CurrentRetryAttempt < RetryCount) 
e.Activity.SetValue(
    ActivityExecutionContext.CurrentExceptionProperty, null);
}

将关闭子活动时逻辑将必须确定如何活动开始关闭状态,并使用 ExecutionResult 属性执行此操作。 由于已关闭状态结束所有活动,则在 ExecutionStatus 不提供来确定将实际的结果所需的信息,但在 ExecutionResult 表示活动 faulted、 成功,还是被取消。 如果子活动成功,然后需要不重试并重试活动只是关闭:

if (e.ExecutionResult == ActivityExecutionResult.Succeeded)
{
  this.SetValue(ActivityExecutionContext.CurrentExceptionProperty, null);
  thisContext.CloseActivity();
  return;
}

如果从结束活动的结果不成功,并且没有达到重试计数,然后必须再次,执行活动,但不是重试之前间隔已过期。在 图 6 ,而不是直接,开始另一次迭代计时器订阅创建使用活动上配置的间隔。

图 6 创建计时器订阅

if (CurrentRetryAttempt++ < RetryCount &&
    this.ExecutionStatus == ActivityExecutionStatus.Executing) {

  this.SetValue(ActivityExecutionContext.CurrentExceptionProperty, null);

  DateTime expires = DateTime.UtcNow.Add(RetryInterval);
  SubscriptionID = Guid.NewGuid();

  WorkflowQueuingService qSvc = 
    thisContext.GetService<WorkflowQueuingService>();
  WorkflowQueue q = qSvc.CreateWorkflowQueue(SubscriptionID, false);
  q.QueueItemAvailable += new EventHandler<QueueEventArgs>(TimerExpired);

  TimerEventSubscription subscription = new TimerEventSubscription(
    SubscriptionID, WorkflowInstanceId, expires);
  TimerEventSubscriptionCollection timers = 
    GetTimerSubscriptionCollection();
  timers.Add(subscription);

  return;
}

在计时器终止时, TimerExpired 方法将调用,如下所示:

void TimerExpired(object sender, QueueEventArgs e)
{
  ActivityExecutionContext ctx = 
    sender as ActivityExecutionContext;
  CleanupSubscription(ctx);
  BeginIteration(ctx);
}

图 7 工作流中的重试活动

这将开始下一个迭代的子活动。通过使用 TimerEventSubscription 类,并向工作流的计时器集合中添加计时器,活动就能够正确地参与持久性和恢复与任何持久性服务当前被配置在运行时。长重试间隔是否整个工作流可以采取了内存之前在计时器终止。

到目前为止已得到满足工作流活动的键的行为。如果一个子活动错误重试活动将不出错。而是将暂停在重试间隔然后尝试再次执行子活动。

最后一步是处理的活动已达到重试计数并子活动已继续失败。在这种情况下,Activity_Faulting 方法不会清除对该子活动异常目标是让为 Normal,活动错误。并重试活动还关闭时将关闭子活动。

重试关闭毕竟重试时尝试已失败,结果是相同像原始工作失败序列中。重试活动可以定义 FaultHandler 活动,并执行所有重试次数后,将只执行这些错误处理程序。使用此模型简化了开发工作流可能需要进行尝试的操作还维护相同的开发体验,工作流开发人员就能够处理错误,如 图 7 所示。

此外,错误处理程序将执行子活动时重试次数已失败,以便工作流开发人员可以选择处理活动错误。确保活动都有机会在每次迭代清理的每个失败的子活动获取调用该 HandleFault 方法。

将您的问题和提出的意见发送至mmnet30@Microsoft.com.

Matt Milner 是人员的在其中他关注连接的系统的技术 Pluralsight,技术的成员。Matt 也是一个独立的顾问,擅长 Microsoft.NET 技术,具有 Windows Workflow Foundation、 BizTalk Server、 ASP.NET 和 Windows Communication Foundation 上的焦点。Matt 与妻子 Kristen 和他的两个儿子居住在明尼苏达。通过在他的博客与 Mattpluralsight.com/community/blogs/Matt.