Click here to Skip to main content
Click here to Skip to main content

An instrumented synchronous/asynchronous event manager utilizing EventHandler and Reflection

, 16 Oct 2002
Rate this:
Please Sign up or sign in to vote.
Implements an instrumented event manager which can be used to invoke event sinks both synchronously and asynchronously. The event sink can be declared using either the System.EventHandler delegate or by reflection.

Sample Image - EventManager.jpg

Introduction

Creating a design pattern bridge between the user interface and its event implementation can often improve the code portability. Furthermore, such an object can be instrumented to record all GUI-implementation interactions.

The event manager presented in this article describes such a bridge. It is intended to separate the event sink from the event source. Especially in MFC programming, event sinks are often implemented as part of the dialog or form class. This makes it nearly impossible to access the implementation, should it be required outside of the scope of the dialog or form. In C#, the form designer also places the event handler in the form class, which is not necessarily desirable.

Furthermore, the event manager provides capability that the default form designer does not provide. It allows for events to be invoked asynchronously, and event sinks can be associated with their sources by either using the System.EventHandler delegate or via reflection.

An Introduction To Events

Events cause the program to break out of its current process flow (usually an idle loop) and execute a specific set of instructions. It is important to get this definition right (and I hope I did a fair job of it) because it then allows further discussion of two important aspects of event management: the event source and the event sink. These terms derive from hardware engineering, where a device “sourced” current and another device was the current "sink". Thus, the event is the go-between for the event source and the event sink.

The event source is the object that generates the event. Typical event sources are GUI controls such as buttons, but they can also be hardware related, such as a timer, a mouse button, a busy indicator, etc.

The event sink is the object that receives the event notification. It performs specific actions based on the event.

Why make the distinction between event source, event and event sink? Why not just say that the event sink is coupled to the event source? Because this removes an important consideration when dealing with events:

  • One event source can source one or more events
  • One or more event sinks can sink one or more events

Both of these are “many to many” relationships. Now, I will state something that may be controversial. In my opinion, one event source should generate only one event, and one event should be received by one and only one event sink. This reduces the possible combinations to one-to-one for event sources to events, and one-to-one for event sinks to events. Why do I have this opinion? Because, an event source that generates multiple events adds a layer of complexity with regards to the sequence that the events are “sinked”. Similarly, a single event (or multiple events) handled by multiple sinks creates the same layer of complexity: what order are the event sinks invoked, and are there synchronization issues between them? This kind of complexity falls outside of the purview of a basic event manager. Furthermore, such a system should be implemented with additional meta-control event manager objects, leaving the core event manager as presented here in tact. Hopefully this would give the programmer a finer level of control and possibly address issues such as semaphores and synchronization between event sinks. The point though is that a set of meta-classes to handle these issues would bring to consciousness the complexity of many-to-many event relationships, and heaven only knows how unconscious programmers can be!

Thus, embedded in the above paragraph is a criticism of .NET’s ability to associate several event sinks to a single event source. All too often, it seems that Microsoft gives the programmer powerful capabilities without explaining the dangers and pitfalls of their usage!

One final note—while I’ve talked about three separate entities, the event source, the event itself, and the event sink, in implementation the concept of the event is abstracted out. It exists in the layers of the operating system and the hardware, but usually the actual concept of the event doesn’t need to be implemented. This differs from a message manager, for example, in which the concept of a "message" is a very real entity and is passed between the addresser and the addressee.

The Event Object Model

The event object model, as illustrated in the above diagram, consists of the abstract base class 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);
}

This class defines a single abstract method, IAsyncResult.

Two abstract classes are derived from the base class, which respectively refine the abstraction a little bit more, into events that are invoked using the System.EventHandler delegate, and events that are invoked using reflection.

The EventHandlerSink abstract class:

/// <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;
    }
}

simply consists of a constructor that takes the System.EventHandler delegate as a parameter.

The ReflectionSink abstract class:

/// <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;
    }
}

maintains information about the reflected method and the instance (if non-static) of the method to invoke and provides two constructors, one to handle static reflected methods and the other to handle reflected methods that are associated with an object instance.

The real work is done in the following four classes, derived from the abstract classes described above. SyncEventHandlerSink (sorry about the alliteration!) handles invoking the event sink as specified in the System.EventHandler delegate and consists of the constructor and the Invoke method, which is very trivial in this case.

/// <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;
    }
}

Compare this to the asynchronous version of the EventHandler delegate call. Here we take advantage of a very poorly documented BeginInvoke method.

/// <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;
    }
}

The synchronous event invocation using reflection isn’t that much more complicated. Here, we use the MethodInfo class to invoke the event sink. The parameters for this object (instance and bindingFlags) have already been set up previously (discussed below).

/// <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;
    }
}

The most interesting implementation is the asynchronous reflected event sink. This requires using an intermediate delegate so that the event sink can be invoked as a separate thread. The MethodInfo class doesn’t appear to have a BeginInvoke as does System.EventHandler.

    /// <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());
            }
        }
    }

To construct the MethodInfo object, a private member function parses the name of the event sink method. This name must be of the form:

assembly/namespace.class/method

The parser is quite simple:

/// <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;
}

The EventManager class consists of several overloaded methods for adding event sinks:

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)…

which allows both static and non-static (instance based) events sink to be specified as either synchronous or asynchronous calls.

Similarly, there are several flavors of the Invoke method based on the information you need to supply:

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)…

For synchronous calls, the Invoke method always returns null. For asynchronous calls, the Invoke method returns an IAsyncResult which can be useful for obtaining thread results.

One caveat here: the way the EventManager is designed, whether the event sink is called synchronously or asynchronously is determined when the event sink is added to the Event collection. It would be fairly trivial to expand this object so that the sync/async determination can be made when the event occurs.

Usage

I’ve included in the project, a very simple demonstration of the usage:

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();
}

Instrumentation

Aside from several warnings issued when an exception occurs, the EventManager logs the event sinks in a text file specified by the Dbg object (see my other articles):

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

Conclusion

In previous programming efforts, I have found that creating an instrumented bridge between GUI generated events and event handlers has been both a productivity enhancement and has helped in debugging complex applications. An event manager can be a general solution which increases the flexibility of your code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Marc Clifton

United States United States
Marc is the creator of two open source projets, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.
 
Marc lives in Philmont, NY.

Comments and Discussions

 
GeneralMany to many relationships of events PinmemberChristian Tratz17-Oct-02 21:44 
GeneralRe: Many to many relationships of events PinmemberMarc Clifton19-Oct-02 21:06 
GeneralRe: Many to many relationships of events PinmemberChristian Tratz19-Oct-02 23:48 
GeneralRe: Many to many relationships of events PinmemberMarc Clifton20-Oct-02 4:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140814.1 | Last Updated 17 Oct 2002
Article Copyright 2002 by Marc Clifton
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid