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

一个利用 EventHandler 和 Reflection 的可检测同步/异步事件管理器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2002年10月17日

6分钟阅读

viewsIcon

99579

downloadIcon

1014

实现了一个可检测的事件管理器,可用于同步和异步地调用事件接收器。事件接收器可以使用 System.EventHandler 委托或通过反射声明。

Sample Image - EventManager.jpg

引言

在用户界面及其事件实现之间创建设计模式桥接通常可以提高代码的可移植性。此外,此类对象可以被检测以记录所有 GUI-实现交互。

本文中介绍的事件管理器描述了这样一个桥接。它旨在将事件接收器与事件源分离。尤其是在 MFC 编程中,事件接收器通常作为对话框或表单类的一部分实现。这使得在对话框或表单范围之外需要访问实现时几乎不可能。在 C# 中,表单设计器也将事件处理程序放在表单类中,这不一定可取。

此外,事件管理器提供了默认表单设计器不提供的功能。它允许异步调用事件,并且事件接收器可以通过使用 System.EventHandler 委托或通过反射与其源关联。

事件简介

事件导致程序跳出其当前进程流(通常是空闲循环)并执行一组特定的指令。正确理解这个定义很重要(我希望我做得不错),因为它允许进一步讨论事件管理的两个重要方面:事件源和事件接收器。这些术语源于硬件工程,其中一个设备“提供”电流,另一个设备是电流“接收器”。因此,事件是事件源和事件接收器之间的媒介。

事件源是生成事件的对象。典型的事件源是 GUI 控件,例如按钮,但它们也可以是与硬件相关的,例如计时器、鼠标按钮、繁忙指示器等。

事件接收器是接收事件通知的对象。它根据事件执行特定的操作。

为什么要区分事件源、事件和事件接收器?为什么不直接说事件接收器与事件源耦合?因为这会消除处理事件时的一个重要考虑因素

  • 一个事件源可以产生一个或多个事件
  • 一个或多个事件接收器可以接收一个或多个事件

这两者都是“多对多”关系。现在,我要声明一些可能有争议的事情。在我看来,一个事件源应该只生成一个事件,一个事件应该由一个且只有一个事件接收器接收。这将可能的组合减少到事件源到事件的一对一,以及事件接收器到事件的一对一。我为什么有这种看法?因为,一个生成多个事件的事件源增加了关于事件“接收”的**顺序**的复杂性。同样,由多个接收器处理的单个事件(或多个事件)会产生相同的复杂性:事件接收器以什么顺序调用,以及它们之间是否存在同步问题?这种复杂性超出了基本事件管理器的范围。此外,此类系统应该使用额外的元控制事件管理器对象来实现,使此处呈现的核心事件管理器保持不变。希望这将为程序员提供更精细的控制级别,并可能解决事件接收器之间的信号量和同步等问题。但关键是,处理这些问题的元类集会使多对多事件关系的复杂性变得**有意识**,天知道程序员会多么无意识!

因此,上面一段话中包含对 .NET 将多个事件接收器关联到单个事件源的能力的批评。似乎微软太经常在不解释其使用危险和陷阱的情况下赋予程序员强大的功能!

最后一点——虽然我谈到了三个独立的实体:事件源、事件本身和事件接收器,但在实现中,事件的概念是抽象出来的。它存在于操作系统和硬件的层中,但通常不需要实现事件的实际概念。这与消息管理器不同,例如,在消息管理器中,“消息”的概念是一个非常真实的实体,并在发送者和接收者之间传递。

事件对象模型

事件对象模型,如上图所示,由抽象基类 EventSink 组成

/// <summary>
/// Abstract base class for all event handlers.
/// </summary>
public abstract class EventSink
{
    /// <summary>
    /// Invokes the event.
    /// </summary>
    /// <param name="sender">The originator of the event.</param>
    /// <param name="args">Parameters to pass to the event handler.</param>
    /// <returns>An IAsyncResult object for
    /// asynchronous events, or null if the event
    /// is invoked synchronously.</returns>
    public abstract IAsyncResult Invoke(object sender, object[] args);
}

此课程定义了一个抽象方法 IAsyncResult

从基类派生出两个抽象类,它们分别将抽象进一步细化为使用 System.EventHandler 委托调用的事件和使用反射调用的事件。

EventHandlerSink 抽象类

/// <summary>
/// Abstract class that manages events declared by
/// specifying the instance and method
/// that will handle the event.
/// </summary>
public abstract class EventHandlerSink : Event
{
    protected EventHandler eh;

    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="eh">The method that will handle the event.</param>
    public EventHandlerSink(System.EventHandler eh)
    {
        this.eh=eh;
    }
}

仅包含一个以 System.EventHandler 委托作为参数的构造函数。

ReflectionSink 抽象类

/// <summary>
/// Abstract class that manages events bound
/// at runtime and declared through reflection
/// (the assembly, namespace, instance, and method names)
/// of the method that will handle the event.
/// </summary>
public abstract class ReflectionSink : EventSink
{
    protected MethodInfo mi;
    protected object instance;
    protected BindingFlags bindingFlags;

    /// <summary>
    /// Constructor using information compiled by
    /// the EventManager object.  This method is used for the
    /// runtime binding of static methods.
    /// </summary>
    /// <param name="mi">A MethodInfo object.</param>
    public ReflectionSink(MethodInfo mi)
    {
        this.mi=mi;
        instance=null;
        bindingFlags=BindingFlags.Public | 
          BindingFlags.NonPublic | 
          BindingFlags.InvokeMethod | 
          BindingFlags.Static;
    }

    /// <summary>
    /// Constructor using the method's object instance
    /// and information compiled by the EventManager object.  This
    /// method is used for the runtime binding of non-static methods.
    /// </summary>
    /// <param name="instance">The instance associated
    /// with the reflection method.</param>
    /// <param name="mi">A MethodInfo object.</param>
    public ReflectionSink(object instance, MethodInfo mi)
    {
        this.mi=mi;
        this.instance=instance;
        bindingFlags=BindingFlags.Public | 
          BindingFlags.NonPublic | 
          BindingFlags.InvokeMethod | 
          BindingFlags.Instance;
    }
}

维护有关反射方法和要调用的方法的实例(如果是非静态)的信息,并提供两个构造函数,一个用于处理静态反射方法,另一个用于处理与对象实例关联的反射方法。

实际工作在以下四个类中完成,它们派生自上述抽象类。SyncEventHandlerSink(抱歉押头韵!)处理根据 System.EventHandler 委托指定调用事件接收器,并由构造函数和 Invoke 方法组成,在这种情况下它非常简单。

/// <summary>
/// SyncEvent handles the synchronous invocation
/// of events bound using an event delegate.
/// </summary>
public class SyncEventHandlerSink : EventHandlerSink
{
    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="eh">The method that will handle the event.</param>
    public SyncEventHandlerSink(System.EventHandler eh) : base(eh)  {}

    /// <summary>
    /// Invoke the synchronous event handler as declared by the delegate.
    /// </summary>
    /// <param name="sender">The originator of the event.</param>
    /// <param name="args">Any parameters passed to the event.</param>
    /// <returns>This method always returns a null
    /// because the event is invoked synchronously.</returns>
    public override IAsyncResult Invoke(object sender, object[] args)
    {
        try
        {
            eh(sender, new EventArgs(args));
        }
        catch (Exception e) 
        {
            Dbg.Warn(false, new DbgKey("EvMgrInvokeError"), e.ToString());
        }
        return null;
    }
}

将其与 EventHandler 委托调用的异步版本进行比较。在这里,我们利用了一个文档非常糟糕的 BeginInvoke 方法。

/// <summary>
/// AsyncEvent handles the asynchronous invocation
/// of events bound using an event delegate.
/// </summary>
public class AsyncEventHandlerSink : EventHandlerSink
{
    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="eh">The method that will handle the event.</param>
    public AsyncEventHandlerSink(System.EventHandler eh) : base(eh) {}

    /// <summary>
    /// Invoke the asynchronous event handler as declared by the delegate.
    /// </summary>
    /// <param name="sender">The originator of the event.</param>
    /// <param name="args">Any parameters passed to the event.</param>
    /// <returns>This method always returns an IAsyncResult.</returns>
    public override IAsyncResult Invoke(object sender, object[] args)
    {
        IAsyncResult res=null;
        try
        {
            res=eh.BeginInvoke(sender, new EventArgs(args), null, null);
        }
        catch (Exception e) 
        {
            Dbg.Warn(false, new DbgKey("EvMgrInvokeError"), e.ToString());
        }
        return res;
    }
}

使用反射的同步事件调用并没有复杂多少。在这里,我们使用 MethodInfo 类来调用事件接收器。此对象(instancebindingFlags)的参数已在之前设置(如下所述)。

/// <summary>
/// ReflectionSyncEvent handles the synchronous
/// invocation of events bound by reflection.
/// </summary>
public class SyncReflectionSink : ReflectionSink
{
    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="mi">A MethodInfo object.</param>
    public SyncReflectionSink(MethodInfo mi) : base(mi) {}
    
    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="instance">The instance associated
    /// with the reflection method.</param>
    /// <param name="mi">A MethodInfo object.</param>
    public SyncReflectionSink(object instance, 
            MethodInfo mi) : base(instance, mi) {}

    /// <summary>
    /// Invoke the synchronous event handler
    /// as determined by the reflection information.
    /// </summary>
    /// <param name="sender">The originator of the event.</param>
    /// <param name="args">Any parameters passed to the event.</param>
    /// <returns>This method always returns a null
    /// because the event is invoked synchronously.</returns>
    public override IAsyncResult Invoke(object sender, object[] args)
    {
        try
        {
            mi.Invoke(instance, bindingFlags, null, 
              new object[] {sender, new EventArgs(args)}, null); 
        }
        catch (Exception e)
        {
            Dbg.Warn(false, new DbgKey("EvMgrInvokeError"), e.ToString());
        }
        return null;
    }
}

最有趣的实现是异步反射事件接收器。这需要使用中间委托,以便可以将事件接收器作为单独的线程调用。MethodInfo 类似乎没有像 System.EventHandler 那样具有 BeginInvoke

    /// <summary>
    /// ReflectionAsyncEvent handles the asynchronous
    /// invocation of events bound by reflection.
    /// </summary>
    public class AsyncReflectionSink : ReflectionSink
    {
        /// <summary>
        /// References our internal asynchronous method. 
        /// This is necessary so that we can
        /// invoke the reflection method asynchronously.
        /// </summary>
        private delegate void RunAsync(object sender, object[] args);
        private RunAsync run;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="mi">A MethodInfo object.</param>
        public AsyncReflectionSink(MethodInfo mi) : base(mi)
        {
            InitializeAsyncDelegate();
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="instance">The instance associated with
        /// the reflection method.</param>
        /// <param name="mi">A MethodInfo object.</param>
        public AsyncReflectionSink(object instance, 
                    MethodInfo mi) : base(instance, mi)
        {
            InitializeAsyncDelegate();
        }

        /// <summary>
        /// Instantiates the delegate for the asynchronous handler.
        /// </summary>
        private void InitializeAsyncDelegate()
        {
            run=new RunAsync(Run);
        }

        /// <summary>
        /// Invokes the reflection method asynchronously.
        /// </summary>
        /// <param name="sender">The originator of the event.</param>
        /// <param name="args">Any parameters passed to the handler.</param>
        /// <returns>This method always returns an IAsyncResult object.</returns>
        public override IAsyncResult Invoke(object sender, object[] args)
        {
            return run.BeginInvoke(sender, args, null, new Object());
        }

        /// <summary>
        /// This method is invoked asynchronously
        /// and invokes the reflection method.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void Run(object sender, object[] args)
        {
            try
            {
                mi.Invoke(instance, bindingFlags, null, 
                  new object[] {sender, new EventArgs(args)}, null); 
            }
            catch (Exception e) 
            {
                Dbg.Warn(false, new DbgKey("EvMgrInvokeError"), e.ToString());
            }
        }
    }

为了构造 MethodInfo 对象,一个私有成员函数解析事件接收器方法的名称。此名称必须采用以下形式

assembly/namespace.class/method

解析器非常简单

/// <summary>
/// Returns a MethodInfo object with the parsed reflect string.
/// </summary>
/// <param name="reflection">A reflection method name, 
/// in the format "assembly/namespace.class/method".</param>
/// <returns></returns>
private static MethodInfo GetMethodInfo(string reflection)
{
    MethodInfo mi=null;
    try
    {
        string[] info=reflection.Split(new char[] {'/'});
        Assembly mainAssembly=Assembly.LoadFrom(info[0]);
        Type[] types=mainAssembly.GetTypes();
        Type type=mainAssembly.GetType(info[1]);
        MethodInfo[] mis=type.GetMethods();
        mi=type.GetMethod(info[2]);
    }
    catch (Exception e) 
    {
        Dbg.Warn(false, new DbgKey("EvMgrNoMethod"), 
                 e.ToString()+"\n"+"Method:"+reflection);
    }
    return mi;
}

EventManager 类包含几个重载方法,用于添加事件接收器

public static void AddSyncEventSink(string name, System.EventHandler eh)…
public static void AddAsyncEventSink(string name, System.EventHandler eh)…
public static void AddSyncEventSink(string name, string reflection)…
public static void AddSyncEventSink(string name, 
                 object instance, string reflection)…
public static void AddAsyncEventSink(string name, string reflection)…
public static void AddAsyncEventSink(string name, 
                 object instance, string reflection)…

它允许将静态和非静态(基于实例)事件接收器指定为同步或异步调用。

同样,Invoke 方法有几种变体,具体取决于您需要提供的信息

public static IAsyncResult Invoke(string name)…
public static IAsyncResult Invoke(string name, object sender)…
public static IAsyncResult Invoke(string name, object[] args)…
public static IAsyncResult Invoke(string name, object sender, object[] args)…

对于同步调用,Invoke 方法始终返回 null。对于异步调用,Invoke 方法返回一个 IAsyncResult,这对于获取线程结果很有用。

这里有一个注意事项:EventManager 的设计方式决定了事件接收器是同步还是异步调用,这在将事件接收器添加到 Event 集合时确定。扩展此对象以使同步/异步确定可以在事件发生时进行是相当简单的。

用法

我在项目中包含了一个非常简单的用法演示

static void Main() 
{
    Dbg.Initialize();
    Dbg.LogOutput("out.txt");

    EventManager.Initialize();

    Events evs=new Events();

    EventManager.AddAsyncEventSink("event1", 
       new EventHandler(MyApp.Events.Event1));
    EventManager.AddSyncEventSink("event2", evs, 
       "csEventManager.exe/MyApp.Events/Event2");
    EventManager.AddAsyncEventSink("event3", 
           "csEventManager.exe/MyApp.Events/Event3");
    EventManager.Invoke("event1");
    EventManager.Invoke("event2");
    EventManager.Invoke("event3");

    MessageBox.Show("Application will exit when you" + 
       " click on the OK button\nand running" + 
       " threads will terminate.");

    Dbg.Close();
}

检测

除了在发生异常时发出的几个警告外,EventManager 将事件接收器记录在由 Dbg 对象指定的文本文件中(请参阅我的其他文章)

Debug Logging Initialized: 10/16/2002 5:50:13 PM
EventManager.Invoke: event1
EventManager.Invoke: event2
EventManager.Invoke: event3

结论

在以前的编程工作中,我发现创建 GUI 生成事件和事件处理程序之间的可检测桥梁既提高了生产力,又有助于调试复杂的应用程序。事件管理器可以是一个通用解决方案,可以增加代码的灵活性。

© . All rights reserved.