天天看點

Solving the Problem with Events: Weak Event Handlers The Problem (A Recap) Some Solutions Obstacles A First Stab The Second Attempt Tweaking Performance Automatic Unregistration Making It Pretty

handles it. The following diagram illustrates the problem.

In the diagram above, there is an object ("eventExposer") that declares an event ("SpecialEvent"). Then, a form is created ("myForm") that adds a handler to the event. The form is closed and the expectation is that the form will be released to garbage collection

but it isn't. Unfortunately, the underlying delegate of the event still maintains a strong reference to the form because the form's handler wasn't removed.

The question is, how do we fix this problem?

Most of you are probably saying "Fix the form you idiot! Remove the handler!" That might seem reasonable—especially if we have control over all of the code. But what if we don't? What if this is a large plug-in based architecture where the event is surfaced

by some event service that exists for the lifetime of the application and the form is in a third-party plug-in for which we don't have the code? In this scenario, we would have a leaky application that couldn't be corrected by simply modifying the form to

properly remove the handler on close.

the ideal solution would be a CLR-level mechanism for creating "weak delegates" which hold weak references to their target objects instead of strong references. Unfortunately, such a mechanism doesn't yet exist (and won't for the foreseeable future) so we

have to roll our own.

couple of years ago that is used in Avalon (err... WPF). This solution works perfectly except that it isn't all that convenient to use. It requires the declaration of a new class that has intimate knowledge of both the container and containee (or subject and

generate types and assemblies on the fly (more on this type of thing later) and the second one requires extra client code and uses Reflection to add add and remove handlers from an event. I should mention however that both solutions do work.

Over the past few years, the community has offered several "magic class" approaches (where a special class is instantiated that wraps your event handler). A "magic class" is an attractive solution because it results in far less client code. Unfortunately, the

a few months ago that takes advantage of a technique that is crucial to getting this right.

I have to admit, there are several obstacles to creating a working solution. First of all, you can't simply inherit from System.Delegate or System.MulticastDelegate and override the appropriate methods. That would be great but it simply isn't possible. You

can't even do it IL (believe me—I've tried). In addition, there isn't a generic constraint for delegates. We can work around that but it certainly makes some code that we might write less elegant.

However, the biggest obstacle is performance. There are two areas of performance that we can consider: 1) adding/removing a handler and 2) actually invoking the handler. On the average, invocation occurs far more often than adding and removing handlers so we'll

focus our attention on that. There are a lot of ways to dynamically invoke a delegate and some of them are very slow when compared with a normal typed delegate invocation. Using a weak delegate or event handler is expected to have some overhead but

we should keep it to a minimum.

Another potentially serious issue is removing the handler. Let me explain what I mean. When we use a "magic class" to wrap an event handler and add it to an event, we create a weak reference to the event handler's target but the event still maintains a strong

reference to our wrapper object. So, when the target object is garbage collected, the wrapper object is kept alive for the same reason that the target wasn't being garbage collected before. Granted, it will likely take far less memory than some large object

(e.g. the form in the diagram that I mentioned earlier) but it's still a leak. That might be acceptable to some of you but there's actually a very simple solution to this so we should deal with it properly.

The last obstacle that I want to mention is how to create a class that will handle any delegate type. I'm going to present a solution that work with any delegate but we can get much higher-performance if we can use a specific delegate type. Because

from the .NET 2.0 framework.

OK, let's dig into some code and take a shot at creating a "magic class" so that I can introduce the players. Here's a very a naive implementation:

using System;

using System.Reflection;

public class WeakDelegate

{

  private WeakReference m_TargetRef;

  private MethodInfo m_Method;

  public WeakDelegate(Delegate del)

  {

    m_TargetRef = new WeakReference(del.Target);

    m_Method = del.Method;

  }

  public object Invoke(params object[] args)

    object target = m_TargetRef.Target;

    if (target != null)

      return m_Method.Invoke(target, args);

}

to create a weak reference to the delegate's target. To use, instantiate a new WeakReference and pass the object that you want to track to its constructor. Then, you can check the WeakReference.IsAlive or WeakReference.Target properties to see if the object

has been garbage collected. Keep in mind that you should never write code like this:

if (m_TargetRef.IsAlive) // race condition!!!!

  return m_Method.Invoke(m_TargetRef.Target, args);

I've seen way too many developers (including myself many moons ago) make this mistake. The problem is that the garbage collector runs on a different thread. This causes a race condition to occur between the calls to m_TargetRef.IsAlive and m_TargetRef.Target.

In other words, m_TargetRef.IsAlive could return true but the garbage collector could kick in and reclaim the target instance before m_TargetRef.Target is called, potentially causing a NullReferenceException to be thrown. The proper way to use this class is

to store the value of m_TargetRef.Target in a local variable. If m_TargetRef.Target doesn't return null, the local variable creates a strong reference to the target instance that can safely be used.

So, using the System.WeakReference is fairly simple but it has one non-obvious problem that I'd like to highlight: it is finalizable but does not implement IDisposable. What does that mean and why is it bad? It means that any System.WeakReference instance will

add a small amount of pressure to the garbage collector because its finalizer method must be called before it can be reclaimed. Internally, WeakReference uses a System.GCHandle to track the target object and it declares a finalizer to clean up that GCHandle.

demand for the unmanaged code security permission. That means that our WeakDelegate class would need the same permission. WeakReference actually gets around those security permissions by using internal methods on GCHandle that don't declare them (it does have

an inheritance demand though—beware inheritors!). Because it requires less security checks, using WeakReference is actually a little bit faster. Sigh...

There is one other important player from our initial attempt that I'd like to point out. The delegate is invoked by calling MethodInfo.Invoke() and passing the target instance and the arguments. This is really slow. Directly invoking the delegate would be much

faster but we can't store a reference to the original delegate because it strongly references the target. So, we have to rely upon late-bound mechanisms that are much slower. This is the main reason that we will shift our focus from wrapping System.Delegate

to System.EventHandler<TEventArgs> shortly.

The robustness of this solution can be improved by making WeakDelegate multi-casting. Currently, it only supports single delegates which is why it is insufficient for using with events. To do this, we would need to use a similar approach to the one presented

With these in place, we could use this class to declare a weak event like this:

public class EventProvider

  private WeakDelegate m_WeakEvent;

  protected virtual void OnWeakEvent(EventArgs e)

    if (m_WeakEvent != null)

      m_WeakEvent.Invoke(this, e);

  public event EventHandler WeakEvent

    add

    {

      m_WeakEvent = WeakDelegate.Combine(m_WeakEvent, value);

    }

    remove

      m_WeakEvent = WeakDelegate.Remove(m_WeakEvent, value);

The main issue here is that all handlers are weak—whether they need to be or not. It would be more flexible to have a weak delegate class that can be used like an ordinary delegate. That way, a subscriber could make specific handlers weak and other handlers

not. The ideal syntax for a subscriber might look something like this:

  public event EventHandler MyEvent;

public class EventSubscriber

  public EventSubscriber(EventProvider provider)

    provider.MyEvent += new WeakDelegate(MyWeakEventHandler);

  private void MyWeakEventHandler(object sender, EventArgs e)

At this point, we're in a bit of a pickle if we want to continue trying to provide a solution that wraps any System.Delegate. In fact, to go down this road, we have use the System.Reflection.Emit classes to generate a new assembly, a new module, a new type,

a WeakReference field, a new method that matches the signature of the delegate and all of the IL necessary to access the WeakReference.Target property, load the parameters onto the stack and call the method referenced by the delegate. Now, this is certainly

possible to do (yes, I did it and it made me weep like a child) but the result is a mechanism that is so heavy that it's almost worthless. Plus, the assembly, module and type are not reclaimable by garbage collection unless you actually create a new AppDomain

to contain the code so it can be unloaded later. However, using another AppDomain will cause invocations of the delegate to cross AppDomain boundaries (ahem... remoting) and it is fraught with peril because you need to work out some serialization mechanism

for the delegate and its target. Are you beginning to realize why I'm not showing any code here? Obviously, this approach kills both performance and memory use. And, since we're trying to create something that's both fast and lightweight, this is simply not

acceptable.

you use EventHandler<TEventArgs> delegate for declaring events if you are targeting .NET 2.0+ anyway. (Microsoft even managed to follow their own recommendation and used this delegate type throughout WCF and WF. They seemingly forgot about WPF though. Sigh...)

With a specific signature, it is much easier to create a "magic" WeakEventhandler class that achieves the syntax that we're looking for.

public class WeakEventHandler<E>

  where E: EventArgs

  private EventHandler<E> m_Handler;

  public WeakEventHandler(EventHandler<E> eventHandler)

    m_TargetRef = new WeakReference(eventHandler.Target);

    m_Method = eventHandler.Method;

    m_Handler = Invoke;

  public void Invoke(object sender, E e)

      m_Method.Invoke(target, new object[] { sender, e });

  public static implicit operator EventHandler<E>(WeakEventHandler<E>

weh)

    return weh.m_Handler;

This version is starting to look a little more like what we want. Being tied to a specific delegate type, we know what the method signature will be and we can create our own method that matches to use as a sort of "delegate proxy". And, thanks to the implicit

conversion we can write the golden syntax that we're looking for.

  public event EventHandler<EventArgs>

MyEvent;

    provider.MyEvent += new WeakEventHandler<EventArgs>(MyWeakEventHandler);

I ran a simple performance test that adds and fires 100 event handlers in the standard way and with the WeakEventHandler class. Here are the results:

Added 100 normal listeners to notifier: 0.000289 seconds.

Added 100 weak listeners to notifier: 0.002701 seconds.

Fired 100 normal listeners: 0.000263 seconds.

Fired 100 weak listeners: 0.001531 seconds.

Obviously, this approach is pretty slow. Invocation through MethodInfo.Invoke() is nearly 6 times slower than normal invocation.

This leaves us with two problems left to solve:

Invocation performance. We're still using MethodInfo.Invoke() and it's slow.

Removal of the weak event handler from the event after the target has been garbage collected.

The second problem is actually very simple so let's look at the performance issue first.

Using MethodInfo.Invoke() is really slow. What we'd really like is some way to generate a delegate that does not actually store a reference to its target but allows us to specify the target when we call it. That way, our invocation performance characteristic

is very similar to that of a standard delegate invocation. In .NET 1.1 there really wasn't a way to create such a delegate but there are two options available to us (that I am aware of) in .NET 2.0.

module (making them effectively global) or a specific type (making them static). Also, unlike the Reflection.Emit story in 1.0/1.1, they have the benefit of being reclaimable by the garbage collector. In fact, the only real downside of LCG is that you have

to spit out the IL of the dynamic method. There is a small hit when creating a dynamic method but, as discussed earlier, the invocation performance is far more important.

using System.Reflection.Emit;

using System.Threading;

  private delegate void EventHandlerThunk(object @this, object sender,

E e);

  private static int g_NextThunkID = 1;

  private EventHandlerThunk m_Thunk;

    m_Thunk = CreateDynamicThunk(eventHandler);

  private EventHandlerThunk CreateDynamicThunk(EventHandler<E> eventHandler)

    MethodInfo method = eventHandler.Method;

    Type declaringType = method.DeclaringType;

    int id = Interlocked.Increment(ref g_NextThunkID);

    DynamicMethod dm = new DynamicMethod("EventHandlerThunk" +

id, typeof(void),

      new Type[] { typeof(object), typeof(object), typeof(E)

}, declaringType);

    ILGenerator il = dm.GetILGenerator();

    // load and cast "this" pointer...

    il.Emit(OpCodes.Ldarg_0);

    il.Emit(OpCodes.Castclass, declaringType);

    // load arguments...

    il.Emit(OpCodes.Ldarg_1);

    il.Emit(OpCodes.Ldarg_2);

    // call method...

    il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method);

    // done...

    il.Emit(OpCodes.Ret);

    return (EventHandlerThunk)dm.CreateDelegate(typeof(EventHandlerThunk));

      m_Thunk(target, sender, e);

Clearly, this code is far more complicated than when it used MethodInfo.Invoke(). First, there is a new delegate declared ("EventHandlerThunk") which has the same signature as the EventHandler that we're wrapping with an additional parameter to take the target.

We create an EventHandlerThunk as a DynamicMethod and when invoking it, we pass the target along with the parameters (see the Invoke method above). Most of the magic code is in the CreateDynamicThunk method. I don't want to get too detailed about the IL that

is generated but it essentially calls the method referenced by "eventHandler" directly instead of through the MethodInfo.Invoke(). Very snazzy.

Using LCG, the invocation performance is not great. I ran the same test that I ran earlier and got these results:

Added 100 normal listeners to notifier: 0.000292 seconds.

Added 100 weak listeners to notifier: 0.010599 seconds.

Fired 100 normal listeners: 0.000272 seconds.

Fired 100 weak listeners: 0.01572 seconds.

Whoa! It turns out that that invoking the LCG solution is almost 58 times slower than normal invocation! Obviously, this is a step backward and not really what we're looking for. Part of this poor performance is caused by the fact that we're actually creating

three method calls here: WeakEventHandler.Invoke() calls the dynamic EventHandlerThunk which calls the real method. The really problem, however, is that the "lightweight" in "lightweight code generation" really refers to memory and not performance. So, this

isn't an acceptable solution for is.

Now, some of you might be scratching your heads right now and saying, "what's an open instance delegate and why haven't I heard of them before?"

An open instance delegate is a delegate that references an instance method but doesn't reference a target. Instead the target is to be specified when the delegate is called. To allow for this, the delegate type of the open instance delegate must declare an

additional parameter that takes the instance that the delegate is called on when invoked (just like our EventHandlerThunk delegate type).

That explains what they are but why haven't you heard of them? Well, there's no language support for them in C# and VB. In order to create one, you have to use an overload of the Delegate.CreateDelegate() method. Open instance delegates were designed to support

Here is the code reworked to use an open instance delegate:

  private delegate void OpenEventHandler(object @this, object sender,

  private OpenEventHandler m_OpenHandler;

    m_OpenHandler = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler), 

      null, eventHandler.Method);

      m_OpenHandler(target, sender, e);

Oops! That compiles but it doesn't actually work. When a WeakEventHandler is instantiated, the call to Delegate.CreateDelegate fails with a System.ArgumentException and the (unhelpful) message: "Error binding to the target method." What the heck is going on?

Well, if you look at the documentation for unbound delegates in C++ it states that "the first parameter of the delegate signature is the type of this for the object you want to call." In other words, using "object" as the type of the first parameter

won't work. It has to match. To correct this problem, we have to add another generic type parameter to our WeakEventHandler class to represent the type of the instance on which the handler is declared. Here's the new version:

public class WeakEventHandler<T, E>

  where T: class

  private delegate void OpenEventHandler(T

@this, object sender, E e);

    T target = (T)m_TargetRef.Target;

  public static implicit operator EventHandler<E>(WeakEventHandler<T,

E> weh)

This works perfectly but now we've broken our client code syntax. The second generic type parameter has to be specified like this:

    provider.MyEvent += new WeakEventHandler<EventSubscriber, EventArgs>(MyWeakEventHandler);

I really dislike the additional generic type parameter because it requires cerebral activity on the part of the user to determine what type needs to be filled in. But, we've corrected the problem that we set out to fix: invocation performance. There is a performance

bump when the open instance delegate is created but that's acceptable because the invocation performance is very good.

Ideally, our WeakEventHandler would be automatically removed from the event on which it is registered when its target is reclaimed by the garbage collector. Unfortunately, that's not possible. There simply isn't any sort of notification when a garbage collection

  public delegate void UnregisterCallback(EventHandler<E>

eventHandler);

  private UnregisterCallback m_Unregister;

  public WeakEventHandler(EventHandler<E> eventHandler, UnregisterCallback unregister)

    m_Unregister = unregister;

    else if (m_Unregister != null)

      m_Unregister(m_Handler);

      m_Unregister = null;

If we pass an UnregisterCallback into the constructor of WeakEventHandler, it will be called once if the WeakEventHandler is invoked after the target has been garbage collected. That neatly solves the problem. Of course, our WeakEventHandler leaks if the event

never fires again but I think that's acceptable. Here is what the client code looks like now:

    provider.MyEvent += new WeakEventHandler<EventSubscriber, EventArgs>(MyWeakEventHandler,

      delegate(EventHandler<EventArgs> eh)

      {

        provider.MyEvent -= eh;

      });

Now that we have a working "magic class" solution, let's take a look at how we might improve the syntax.

The most important thing is to get rid of the additional generic type parameter that we were forced to add. It just requires too much thinking to assign it properly. The use of an anonymous method might confuse the code for some but it's easy enough to move

that into a separate method.

public delegate void UnregisterCallback<E>(EventHandler<E>

eventHandler)

  where E: EventArgs;

public interface IWeakEventHandler<E>

  EventHandler<E> Handler { get; }

public class WeakEventHandler<T, E>: IWeakEventHandler<E>

  private UnregisterCallback<E> m_Unregister;

  public WeakEventHandler(EventHandler<E> eventHandler, UnregisterCallback<E> unregister)

      null, eventHandler.Method);

      m_OpenHandler.Invoke(target, sender, e);

  public EventHandler<E> Handler

    get { return m_Handler; }

public static class EventHandlerUtils

  public static EventHandler<E> MakeWeak<E>(EventHandler<E>

eventHandler, UnregisterCallback<E> unregister)

    where E: EventArgs

    if (eventHandler == null)

      throw new ArgumentNullException("eventHandler");

    if (eventHandler.Method.IsStatic || eventHandler.Target == null)

      throw new ArgumentException("Only

instance methods are supported.", "eventHandler");

    Type wehType = typeof(WeakEventHandler<,>).MakeGenericType(eventHandler.Method.DeclaringType, typeof(E));

    ConstructorInfo wehConstructor = wehType.GetConstructor(new Type[] { typeof(EventHandler<E>), 

      typeof(UnregisterCallback<E>) });

    IWeakEventHandler<E> weh = (IWeakEventHandler<E>)wehConstructor.Invoke(

      new object[] { eventHandler, unregister });

    return weh.Handler;

That looks like a lot more code but it's not too bad. Our magic class has remained relatively unchanged except that the UnregisterCallback delegate type is no longer nested inside and it has a Handler property to implement the new IWeakEventHandler<E> interface.

The meat is in EventHandlerUtils.MakeWeak<E>(). This generic method performs the reflection magic necessary to instantiate WeakEventHandler<T, E> with its generic type parameters dynamically filled in. This is where the IWeakEventHandler<E> interface becomes

handy. Without it, we would have to use reflection to access the m_Handler field.

At this point, I expect that you're wondering what the performance characteristics of this approach are. Well, using the same performance test with the EventHandlers.MakeWeak<E>() method, I got the following results:

Added 100 normal listeners to notifier: 0.000298 seconds.

Added 100 weak listeners to notifier: 0.011509 seconds.

Fired 100 normal listeners: 0.000288 seconds.

Fired 100 weak listeners: 0.000745 seconds.

This is a much better performance story and closer to what we're looking for. This test demonstrates that using an open instance delegate in the WeakEventHandler class results in performance that is only about 2.5 times slower than standard delegate invocation.

That is actually about what we should expect since a WeakEventHandler causes two delegate invocations.

There is room for improvement in EventHandlerUtils.MakeWeak(). A final version could cache the ConstructorInfo (probably using the .NET 2.0 reflection token APIs) and use the values of the two generic type parameters as a composite key. Also, a dynamic method

might be employed (and cached) so that ConstructorInfo.Invoke() doesn't have to be called. So, there are several tweaks available.

We have improved the client code syntax a bit. It's easier to use now:

    provider.MyEvent += EventHandlerUtils.MakeWeak<EventArgs>(MyWeakEventHandler,

The biggest syntax improvement occurs with C# 3.0. When calling EventHandlerUtils.MakeWeak in C# 3, we can use a lambda expression instead of an anonymous method like this:

    provider.MyEvent += EventHandlerUtils.MakeWeak<EventArgs>(MyWeakEventHandler, 

      eh => provider.MyEvent -= eh);

Even better, the EventHandlerUtils.MakeWeak method can be turned into an extension method for EventHandler<TEventArgs>. Then we can call it like this:

    provider.MyEvent += new EventHandler<EventArgs>(MyWeakEventHandler).MakeWeak(eh

=> provider.MyEvent -= eh);

Now that's syntax that I can really get behind. If you're wondering how you might use this from within the class that declares an event to make all subscribed event handlers weak, here's one possibility:

  private EventHandler<EventArgs> m_MyEvent;

MyEvent

      m_Event += value.MakeWeak(eh => m_Event -= eh);

Have fun!