[本文转自《Solving the Problem with Events: Weak Event HandlersMarch 26, 2007: Added performance tests, corrected several typos and clarified the performance issues with the lightweight code generation approach.]

1、The Problem (A Recap)

[转]如何解决事件导致的Memory Leak问题:Weak Event HandlersAs discussed last time, delegates can produce memory leaks in our applications if not used carefully. Most often, this happens with events when we add a handler to an event, forget to remove it and the object on which the event is declared lives longer than the object that 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.

2、Some Solutions

As I mentioned in my before, 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.

Greg Schechter presented a well-known solution a 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 observer). Schechter's essay inspired several developers in the community to come up with architectures that make this approach easier to use and a couple of articles were submitted to code project. Notably, "Observable property pattern, memory leaks, and weak delegates for .NET" and "An Easy to Use Weak Referenced Event Handler Factory for .NET 2.0". Unfortunately, neither of these solutions are ideal. First, both of them have several support classes. In addition, the first is extraordinarily heavy because it uses Refelection.Emit to 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 majority of these offerrings got it wrong. Most of them make the mistake of creating a weak reference to the event handler itself rather than the target of the event handler. Shawn A. Van Ness eventually removed his solution because of problems. Ian Griffiths kept his available with a note that it doesn't actually work. Even Joe Duffy (of whom I'm a big fan) presented a "magic class"-type solution that doesn't really do the job. A very interesting approach that does work is the one presented by Xavier Musy. Xavier chose to essentially re-implement System.MulticastDelegate but it suffers from performance issues and is not safe multi-threaded scenarios. Finally, Gregor R. Peisker presented a real working (and performant) solution just a few months ago that takes advantage of a technique that is crucial to getting this right.

3、Obstacles

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 of that, I'll focus most of my attention on that only work with the handy generic System.EventHandler<TEventArgs> delegate from the .NET 2.0 framework.

4、A First Stab

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;
   3:  
class WeakDelegate
   5: {
private WeakReference m_TargetRef;
private MethodInfo m_Method;
   8:  
public WeakDelegate(Delegate del)
  10:     {
new WeakReference(del.Target);
  12:         m_Method = del.Method;
  13:     }
  14:  
object[] args)
  16:     {
object target = m_TargetRef.Target;
  18:  
null)
return m_Method.Invoke(target, args);
  21:     }
  22: }

相关文章: