代码改变世界

《WF编程》系列之41 - 承载工作流:日志和配置文件

2008-04-21 09:37  Windie Chai  阅读(4772)  评论(15编辑  收藏  举报

6 工作流宿主

Windows Workflow 其实是一个Runtime,而不是应用程序。在启动工作流之前,宿主进程必须先加载并运行工作流Runtime。宿主进程告诉Runtime要创建什么类型的工作流,Runtime则负责管理工作流的生命周期并通知宿主进程重要的生命周期事件,比如工作流的完成和终结。Runtime并不挑剔它的宿主,宿主可以是台式机上的智能客户端应用程序,也可以是机架上的服务器中运行的ASP.NET进程。所有宿主进程需要做的只是加载.NET 3.0工作流程序集而已。
宿主也可以通过在工作流的基本功能集之上添加附加服务来定制工作流Runtime。这些服务可以用来为长时间运行的工作流提供持久化支持,也可以用来为监视工作流执行情况而提供跟踪支持。还记得之前我们介绍过的ExernalDataExchangeService吗?当需要在工作流和宿主之间通信时,我们就在Runtime中添加了这个服务。但是并不是所有的应用程序都需要这个功能,所以这个服务是一个可选组件,如果我们需要,我们就添加它。
在本章中,我们将深入探讨Windows Workflow Runtime和它的服务。首先来认识一下日志(Logging)、跟踪(Trackng)和工作流Runtime的配置选项;接着是调度服务(Scheduling Services),调度服务为Runtime执行工作流提供了线程支持;然后是持久化服务,持久化服务允许我们将工作流状态信息保存到持久存储设备;最后,我们将介绍跟踪服务,跟踪服务允许我们监视工作流的执行过程。

6.1 Workflow Runtime

WorkflowRuntime类是宿主通往Widnows Workflow的门路。宿主创建了此类的实例,然后订阅数个下表中的事件。这些事件会报告Runtime所执行的所有工作流实例的状态。
名称 触发原因
WorkflowAborted 当工作流实例被中断时
WorkflowCompleted 当工作流实例完成时
WorkflowCreated 当成功调用CreateWorkflow并完成时
WorkflowIdled 当工作流进入空闲状态时
WorkflowLoaded 当持久化服务恢复了工作流实例时
WorkflowRersisted 当持久化服务存储了工作流实例时
WorkflowResumed 当工作流再暂停之后继续执行时
WorkflowStarted 当工作流开始执行时
创建Runtime并订阅事件的代码相对来说并不难。下面的示例创建了Runtime、订阅了WorkflowCompleted和WorkflowTerminated事件并且运行了新的工作流实例。
using (WorkflowRuntime runtime = new WorkflowRuntime())
using (AutoResetEvent reset = new AutoResetEvent(false))
{
runtime.WorkflowCompleted 
+= delegate { reset.Set(); };
runtime.WorkflowTerminated 
+= delegate { reset.Set(); };
runtime.StartRuntime();
WorkflowInstance instance;
instance 
= runtime.CreateWorkflow(typeof(SimpleWorkflow));
instance.Start();
reset.WaitOne();
}

在上面的代码中我们可以发现,WorkflowRuntime类提供了诸如CreateWorkflow和StartRuntime这样的公开方法来管理工作流及其执行环境。这些方法可以启动或者停止Runtime、创建并获取工作流、添加并删除Runtime的内部服务。稍后我们将细致的讨论一些这样的方法。
虽然在现实中,我们并不希望创建了一个Runtime却只执行一个工作流,大多数应用程序会让Runtime伴随着自己的生命周期并执行着多个工作流。接下来,我们会用简单的代码来清晰明了的演示WF Runtime的日志(Logging)功能。

6.1.1 Workflow Runtime日志

.NET Framework在System.Diagnostics命名空间下提供了新的跟踪API。Windows Workflow使用这个跟踪API来记录Runtime内部发生的信息。跟踪信息要比WorkflowRuntime类提供的公开事件详细得多。如果想要获取跟踪信息,我们首先需要在工作流Runtime中启用至少一个跟踪源。 
什么时候使用跟踪?
不要在程序正常操作期间使用跟踪,因为跟踪会引起性能瓶颈。但是,在追寻低性能问题和引发异常的原因时,跟踪就会起很大的作用。比如,我们不能使用调试器来调试WF Runtime内部的代码,但我们可以启用日志来查看它内部正在发生的事情。

WF有五个跟踪源可供我们使用。这些跟踪源可以提供位于Windows Workflow中不同功能区域的诊断信息。我们可以在代码中启用它们,也可以在应用程序配置文件中启用它们。下面的配置文件示例了如何配置这五个跟踪源:

<configuration>
  
<system.diagnostics>
    
<switches>
      
<add name="System.Workflow.Runtime" value="All" />
      
<add name="System.Workflow.Runtime.Hosting" value="All" />
      
<add name="System.Workflow.Runtime.Tracking" value="Critical" />
      
<add name="System.Workflow.Activities" value="Warning" />
      
<add name="System.Workflow.Activities.Rules" value="Off" />
      
<add name="System.Workflow LogToFile" value="1" />
    
</switches>
  
</system.diagnostics>
</configuration>

节中,我们看到了每个跟踪源的名称。这些源提供了不同区域的信息。例如,当工作流验证规则集中的规则时,我们将看到来自System.Workflow.Acivities.Rules这个跟踪源的诊断信息。
我们可以为每一个跟踪源配置一个值(value),这个值用来标识所需信息的类型。允许填入的值包括Critical(严重错误)、Error(错误)、Warning(警告)、Information(信息)和All(全部)。All值会告诉跟踪源我们需要所有跟踪信息。Critical值告诉跟踪源只发布严重错误信息。
最后一个节点(LogToFile)是一个跟踪开关,它的作用是告诉WF把所有跟踪信息输出到日志文件。日志文件名为WorkflowTrace.log,我们可以在应用程序工作的目录找到它。下图演示了日志文件的内容。每一行输出都包含了跟踪源的名称和文本消息。

当我们在使用Visual Studio调试器时,跟踪信息也会同时显示在Visual Studio的输出窗口中。如果我们想要发送日志信息到其它文件,可以创建一个新的跟踪监听器(Trace Listener)。我们可以在配置文件或代码中创建跟踪监听器。下面是一个简单的例子,在控制台程序中发送跟踪信息到命令行:

TraceListener console;
console = new TextWriterTraceListener(Console.Out, "console");
Trace.Listeners.Add(console);
using (WorkflowRuntime runtime = new WorkflowRuntime())
using (AutoResetEvent reset = new AutoResetEvent(false))
{
}

在上面的代码工作之前,我们还需要配置工作流Runtime,让它把跟踪信息发送到跟踪监听器。将下面的XML添加到节中即可:

<add name="System.Workflow LogToTraceListeners" value="1" />

当然,Runtime并不是只会诊断和跟踪,下面我们来讨论一下Runtime服务的配置。

6.1.2 Workflow Runtime配置文件

向Runtime中添加服务的方法有两种:命令法(Imperative approach)和声明法(Declarative approach)。
声明法使用应用程序配置文件来配置服务。
命令法在代码中创建服务并通过使用WorkflowRuntime类的AddService将它们添加到Runtime中。
命令法我们已经接触过很多次了,下面来看看声明法。

6.1.2.1 工作流配置节

在.NET配置文件系统中,配置节都由相应的节处理器(section handlers)来管理。WF提供了WorkflowRuntimeSection类来操作这它的专属配置节。下面的配置文件框架配置了一个节处理器并提供了一个配置节来初始化WF Runtime。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<configSections>
    
<section 
      
name="MyRuntime" 
      type
="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime,Version=3.0.00000.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35"/>
  
</configSections>
  
<MyRuntime>
    
<CommonParameters>
      
<!-- add parameters used by all services -->
    
</CommonParameters>
    
<Services>
      
<!-- add services -->
    
</Services>
  
</MyRuntime>
</configuration>

在上面的配置文件中,我们将WorkflowRuntimeSection类作为节处理器分配给MyRuntime节。这种分配发生在元素中,当.NET Runtime需要读取MyRuntime节时,它就会实例化WorkflowRuntimeSection,并使其工作。
MyRuntime节包含了工作流Runtime的真实配置。一个工作流配置节包含两个子结点。CommonParameters节点保存着所有工作流服务都会用到的名值对参数。比如我们有多个服务需要同样的数据库连接字符串,我们就可以把这个连接字符串添加到CommonParameters内部,而不是为每个服务单独配置。Services节点包含了我们向工作流Runtime添加的服务类型。
为了让工作流Runtime获取到正确的配置,我们需要告诉Runtime配置节的名称。通过在WorkflowRuntime类的构造函数中将节名称作为参数传递,Runtime便可以找到正确的配置节,并读取配置。下面的代码将会用MyRuntime节中的配置来初始化Runtime:
WorkflowRuntime runtime = new WorkflowRuntime("MyRuntime")
我们可以在配置文件中添加多个工作流配置节。每一节都需要节处理器。定义节处理器的name属性需要匹配一个工作流配置节名称,type属性则总是WorkflowRuntimeSection类的完全限定名(Fully qualified name)。
在一个应用程序中可以启动多个工作流Runtime。我们可以通过提供多个配置节来给每个Runtime不同的配置。当工作流需要不同的执行环境时,这种可以使用多个Runtime的能力就会非常有用。我们将在下一节讨论工作流调度服务时看到这个例子。