65.9K
CodeProject 正在变化。 阅读更多。
Home

使用发布者订阅者模式的事件管理/日志记录

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (4投票s)

2005年10月21日

9分钟阅读

viewsIcon

60501

downloadIcon

574

解释基于发布者订阅者模式的事件管理/日志记录组件。

引言

您很有可能在您的某个应用程序中遇到过需要记录事件的情况。这可能源于需要跟踪长时间运行的作业或记录调试类型信息。无论情况如何,这在应用程序中都是一个相当普遍的需求。

嗯,我也有这种需求,所以我构建了一个小型的事件管理/日志记录组件,可以轻松定制以满足与事件管理和日志记录相关的各种需求。

在本文中,我解释了我创建的事件管理组件。我的事件管理组件可以执行或提供钩子来执行:

  1. 将事件异步记录到各种类型的事件日志。
  2. 跟踪长时间运行的进程或调试类型信息。
  3. 将事件记录到文件、数据库或应用程序中实现的某些内存对象。
  4. 将事件记录到不同的事件日志中。例如,安全事件可以记录到文件中,而其他信息类型事件可以直接记录到 Windows 应用程序事件日志等。
  5. 打开和关闭事件日志记录。此外,微调事件的记录方式。例如,在开发过程中,只记录调试类型事件,但在部署过程中则不记录。
  6. “监听”应用程序引发的事件。
  7. 自定义每个事件日志中记录的事件类型。例如,一个事件日志可能只关心安全事件,而另一个事件日志可能同时关心调试和安全事件。
  8. 按记录的事件类型过滤事件日志。例如,过滤所有错误事件。

如果您需要这些功能中的任何一个,您可以使用我的组件,或者至少使用我在此处介绍的一些概念。

那么,这个完整的事件管理组件是如何工作的呢?在深入细节之前,让我给您一个简要的概述。

概述

在应用程序中,我有一个全局事件管理器的概念。当发生任何值得注意的事件时,应用程序负责通知全局事件管理器。简而言之,应用程序“抛出”事件,并让事件管理器处理其余部分。这是通过一个名为 HandleEvent() 的简单静态方法实现的。HandleEvent 方法中的一个参数是一个名为 IEvent 的接口。在应用程序中,您可以实现自己的 IEvent 版本。此接口定义了事件发生位置、事件名称、事件时间、任何相关消息等信息。我的想法是,应用程序可以有许多 IEvent 的自定义实现,每个都可以在适用时随时随地引发。

事件管理器最初加载了一些在 app.config 文件中定义的默认订阅者。当应用程序引发事件时,事件管理器会通知任何订阅者。然后,每个订阅者依次确定事件的记录方式和位置。每个订阅者都可以配置为处理所有事件或特定事件集。同样,订阅者可以配置为启用或禁用,即是否进行日志记录。同样,app.config 是指定这些设置的位置。

最后,事件管理器公开了方法 AddSubscriberRemoveSubscriber。这些方法允许在运行时添加和删除额外的订阅者。这在长时间运行的作业等情况下效果很好。例如,在长时间运行的作业期间,方法可以在特定的检查点引发事件,允许任何注册的订阅者接收这些通知并将相关的状态信息中继给最终用户。

值得注意的是,事件管理组件是基于发布者-订阅者模式的(设计模式是另一个远远超出本文范围的宏大主题)。

在深入组件中每个类的细节之前,让我提一下组件上下文中使用的一些关键概念。

Reflection(反射)

在应用程序启动时使用反射,以加载事件管理器的正确实例以及 app.config 中定义的默认订阅者。app.config 定义了 IEventManagerIEventLogger(s) 和 IEvent(s) 的类类型(见下文)。

线程

在示例应用程序中使用线程异步调用长时间运行的作业。更重要的是,线程用于在通过事件管理器引发事件时通知订阅者。

委托(Delegates)

委托是一种特殊类型的托管类,允许您使用类型安全的函数指针。每个委托类型都基于单个方法签名。在事件管理组件中,每个事件“记录器”或订阅者类都实现了一个名为 OnReceiveEvent 的特定函数。每当事件“抛给”事件管理器时,事件管理器都会为每个注册的订阅者调用此方法。

工厂模式

在启动时与反射一起使用工厂模式,以创建应用程序中正在实现的事件管理器的正确实例。EventManagerSettingsHandler 负责加载包含所有正确缓存对象的事件管理器。

最后,我还想提一下,可以下载一个功能齐全的示例,您可以在其中“闲暇”时探索所有详细信息。

好的,既然我们已经定义了事件管理组件和使用的关键概念,现在让我们描述主要的类/接口及其作用。之后,我们将深入我们都了解和喜爱的 .NET 代码。

类和接口

  • IEventManager

    这是一个定义全局事件管理器的接口,它将接收来自应用程序的所有事件通知。此接口的实现是负责接收应用程序引发的事件的类。同样,它也负责通知任何注册的订阅者。

  • IEventLogger

    这是一个定义事件管理器的订阅者的接口。我互换使用“订阅者”和“记录器”这两个词,因为它们实际上是一回事。从一个角度来看,IEventLogger 的实现订阅了发送给事件管理器的事件,而从另一个角度来看,它们处理事件的物理记录。IEventLogger 的每个实现都将定义它应如何处理从 IEventManager 接收到的事件。例如,一个记录器可能写入文件,而另一个可能写入应用程序事件日志。

  • IEvent

    这是一个定义应用程序中引发的事件的接口。用户可以选择实现其应用程序特定的 IEvent 版本。

  • EventManagementSettingsHandler

    这个类负责读取应用程序配置文件并分别实例化 IEventManagerIEventLogger(s) 的相应类类型。在示例代码中,IEventManager 的实际实现是 ApplicationEventManager 类。请注意,本文的范围不包括读取 app.config 中的自定义节,因此只需说明 EventManagementSettingsHandler 包含所有使其能够读取 app.config<EventManagment> 节的管道。还有一些其他的辅助类,但本文不涵盖它们。

  • EventManagementFactory

    这个类包含一个名为 getEventManager 的静态方法。此方法提供对由 EvenManagementSettingsHandler 创建的事件管理器的访问。请注意,一旦创建了事件管理器,它就会被缓存到工厂中。因此,应用程序总是调用 getEventManger 来获取应用程序事件管理器的句柄。

代码

有了所有这些背景知识,现在让我们卷起袖子看看代码。我们需要检查的第一项是 app.config 中的一个节点。

<!--Event Management Section for Logging Will create the correct 
Event Managemer object through reflection and set up 
all the default subscribers to the Event Management --> 

<EventManagement> 
  <daEventManager xmlns="CSI.Common.Framework.EventManagement.EventManager" 
       name="MyEventManager" 
       type="CSI.Common.Framework.EventManagement.ApplicationEventManager" 
       mode="on"> 
    <daEventLogger name="ApplicationLogger" mode="on" 
      type="CSI.Common.Framework.EventManagement.MessageQueueLogger"> 
      <!--Since Events are defined then only accept these type of events--> 
      <daEvent name="ApplicationEvents" mode="on" 
        type="CSI.Common.Framework.EventManagement.ApplicationEvent" /> 
    </daEventLogger> 
    <!-- Security Logger to only Receive Security Events.
     Note this is not implemented 
    <daEventLogger name="SecurityLogger" mode="on" type="SecurityLogger"> 
      <daEvent name="SecurityEvents" mode="off" type="SecurityEvent"/> 
    </daEventLogger> 
    Could Implement your own file Logger. 
    Note, not implement only there for sample purposes 
    <daEventLogger name="FileLogger" mode="on" 
      type="CSI.Common.Framework.EventManagement.FileLogger"> 
      All Events Logged when no events defined 
    </daEventLogger>--> 
  </daEventManager> 
</EventManagement>

正如我之前所述,EventManagementSettingsHandler 负责读取 app.config 的这一节,以创建事件管理器的全局实例。在上面的节点中,<daEventManager> 标签的属性类型定义了 .Common.Framework.EventManagement.ApplicationEventManager。这是将为应用程序创建的事件管理器的类类型。一旦创建,下一步是创建所有订阅者。对于 <daEventManager> 标签下的每个 <daEventLogger>,将创建相应的订阅者并将其注册到 ApplicationEventManager。请注意,第一个 <daEventLogger> 定义了类型 CSI.Common.Framework.EventManagement.MessageQueueLogger。因此,我们的事件管理器将至少有一个 MessageQueueLogger 类型的订阅者。关于节点的最后一个值得注意的项目是每个订阅者都将处理 <daEvent> 标签中定义的所有事件。如果未定义 <daEvents> 标签,则相应的订阅者/记录器将处理所有事件。

实现这一切的代码在 EventManagerFactory.GetEventManger 类中。特别注意以下部分:

If _eventManager Is Nothing Then
  myManagerSettings = _
    CType(ConfigurationSettings.GetConfig("EventManagement" & _ 
    "/daEventManager"), daEventManager) 
  'First we load the correct event manager 

  myEventManager = _
    Activator.CreateInstance(Type.GetType(myManagerSettings.type)) 
  'Next we load up any listeners 

  If myManagerSettings.daEventLogger Is Nothing = False Then 
    For Each myLoggerSettings InmyManagerSettings.daEventLogger 
      If myLoggerSettings.mode = "on" Then 
        myLogger = _
          Activator.CreateInstance(Type.GetType(myLoggerSettings.type)) 
        myLogger.LoggerName= myLoggerSettings.name
        myEventManager.AddSubscriber(myLogger) 
        'For Each Logger Add the Specific Events that it listens to 

        'If none defined then it will listen to all event types passed in 

        If myLoggerSettings.daEvent Is Nothing = False Then 
          For Each myEventSettings In myLoggerSettings.daEvent 
            If myEventSettings.mode = "on" Then 
              myEvent = _
                Activator.CreateInstance(Type.GetType(myEventSettings.type)) 
              myLogger.AddHandledEvent(myEvent) 
            End If 
          Next 
        End If 
      End If 
    Next 
  End If 
  _eventManager = myEventManager 
End If

现在我们已经加载了事件管理器,让我们检查一下我们如何引发事件以及事件引发时会发生什么。为了引发事件,我们将在应用程序中调用类似以下的代码:

CSI.Common.Framework.EventManagement.EventManagerFactory._
  GetEventManager().HandleEvent(New _
  CSI.Common.Framework.EventManagement.ApplicationEvent("Start LongEvent", _
  Me, "Start Long Event", _
  CSI.Common.Framework.EventManagement.EventTypes.Information), Me)

此代码将创建一个新的应用程序事件,并将其发送到我们的事件管理器。事件管理器接下来所做的是将此事件广播给任何正在监听的订阅者。

让我们检查一下 ApplicationEvent 中两个重要方法的代码,然后描述它们的功能。

Public Sub HandleEvent(ByRef anEvent As IEvent, _
       ByRef source As Object) Implements _
       CSI.Common.Framework.EventManagement.IEventManager.HandleEvent 
  'Now Call allthe Broadcasters and tell them about the event 

  OnReceiveEvent(source, anEvent) 
End Sub

Public Sub OnReceiveEvent(ByRef sender As Object, ByRef evt AsEventArgs) 
  Dim targets() As System.Delegate 
  SyncLock Me 
    Try 
      targets = Me.EventReceivedEvent.GetInvocationList() 
    Catch ex As Exception 
      'No listeners/subscribers 

      Exit Sub 
    End Try 
  End SyncLock 
  'Call all broadcasters asychronously 

  If targets.Length > 0 Then 
    Dim listener As _
     CSI.Common.Framework.EventManagement.IEventManager.EventReceivedEventHandler 
    Dim ar As IAsyncResult 
    For Each listener Intargets 
      ar =listener.BeginInvoke(sender, _
          CType(evt,System.EventArgs), Nothing, Nothing) 
    Next 
  End If 
End Sub

正如我们所看到的,HandleEvent 调用 OnReceiveEventOnReceiveEvent 的作用是遍历 IEventManager 中声明的事件 EventReceived 的所有委托引用,并调用每个注册订阅者中的相应函数 OnReceiveEvent。请记住,委托是一种特殊类型的托管类,允许您使用类型安全的函数指针。我们通过 AddSubcriber 方法将所有这些指向 OnReceiveEvent 的指针注册到我们的订阅者对象中。

Public Sub AddSubscriber(ByRef aSubscriber As IEventLogger)_
           Implements IEventManager.AddSubscriber 
  Try 
    If Me._subscribers.Contains(aSubscriber) Then 
      Exit Sub 
    End If 
    Me._subscribers.Add(aSubscriber.LoggerName, aSubscriber) 
    AddHandler Me.EventReceived, _
               AddressOf aSubscriber.OnReceiveEvent 
  Catch ex As Exception 
    'Do nothing if already in the collection 

    'Throw ex 

  End Try 
End Sub

值得一提的是 OnReceiveEvent 方法中的 SyncLock。它的作用是锁定事件管理器,以便我们不会在获取所有已注册订阅者时被应用程序的某些其他部分添加另一个订阅者而陷入困境。这样我们就可以确保在事件引发时所有订阅者都会收到通知。

要讨论的最后一个想法是当我们的事件管理器调用订阅者对象中的 OnReceiveEvent 函数时会发生什么。很酷的是,这真的取决于每个单独的实现。如果我们看一下 MessageQueueLogger 类,这是我示例中的一个默认实现,我们会看到:

Public Overrides Sub OnReceiveEvent(ByRef sender _
                 As Object, ByRef evt As System.EventArgs)
  Try
    If Me._handledEvents.Count = 0 Then
      Me._eventLog.Enqueue(evt)
    Else
      If Me._handledEvents.ContainsValue(evt.GetType) Then
        Me._eventLog.Enqueue(evt)
      End If
    End If
  Catch ex As Exception
    Throw ex
  End Try
End Sub

在这个特定的实现中,我们首先检查订阅者是否已设置为处理引发的事件。如果是,那么我们将事件放入我们的消息队列。其他实现可能会获取此事件并将其直接放入 Windows 事件日志中。

此时,如果您觉得这个事件管理组件可以用于/定制您的应用程序,那么我认为最好的方法是下载并逐步查看示例源代码。示例代码为了示例目的进行了精简。我建议遵循表单 GenericForm 中的 Sub Button1_Click。此表单被设置为事件管理器的订阅者。它还将展示如何从事件管理器中添加订阅者、处理事件和删除订阅者。

就是这样。请注意,示例代码为了本文目的进行了精简,但它功能齐全。祝您好运,如果您有任何进一步的问题,请随时通过 brianmrush@yahoo.com 与我联系。

尽情享用!

© . All rights reserved.