Simulating “Weak Delegates” in the CLR


Introduction




What follows may seem like a fairly obscure topic relating to lifetime management in the CLR, but for those who have hit upon this scenario (and they may not even know they have), this is a very helpful pattern to know about.




In some of the development of Avalon, we hit upon a pattern for associating event handlers from a generic notion of Containee to a generic notion of Container.  This results in strong CLR references to the Container, and can thus prevent garbage collection of the Container so long as the Containee is alive.  This is similar to the problem that WeakReferences in the .NET Framework were designed to solve, but somewhat different in that this scenario is specific to events.




This becomes a real issue if:




a)      the Containee outlives the Container (or at least outlives the intended lifetime of the Container), and,

b)      the Container is substantially more heavyweight than the Containee, and thus results in substantial resources being held onto


This certainly doesn’t happen all the time, but when it does, one needs a good pattern and solution for dealing with it.  The rest of this post lays out such a pattern and solution.




The Problem



The specific instance of this problem we’re facing in the Avalon is that there’s a key class in the system called a Visual.  A Visual contains a Transform (for 2D transformation).  That Transform need not be used only in one place, and may be contained by a bunch of other Visuals (and other objects) as well.  Furthermore, the Transform can change, and when it changes, the Visual (or Visuals) needs to be notified.  From the programmer’s point of view, there’s no reference from the Transform to the Visual that contains it, since the programmer simply thinks of assigning a Transform into a Visual, not vice-versa. Therefore, there’s no reason to expect that if I hang on to a Transform, that I’m keeping the Visuals alive that reference that Transform.  However, that’s exactly the case, since the Visuals are listeners on the events published by the Transform.



This is simply a specific case of a much more general problem of Containers and Containees, where the Containee has a hidden reference (from the point of view of the programmer) to the Container, and thus forces the Container to stay alive.  Here’s the situation I’m talking about:



Furthermore, since the Containee can be used in multiple places, we get these graphs:



 



And this is even more insidious than the previous one since having one of the Containers still be alive forces the other one to stay alive.  This gets worse the more places the Containee is used.



A Solution



An ideal solution to this would be some explicit CLR notion of “Weak Delegates”.  That is, a delegate that can be used to attach to an event whose target is inherently a weak reference.  The CLR doesn’t currently have such a feature, so we must make do with what we have.  Thus, given that there is no such first-class notion of a “weak delegate”, we can employ a pattern to get a very good approximation of the problem we’re trying to solve.



The approach is to have the Containee register for an event not on the Container, but on a class that derives from WeakReference and implements an event with the same signature as that on the Container.  It then holds a reference to the container as the WeakReference’s “target”.  This class, in the code below, is called a WeakContainer, and approximates what a “Weak Delegate” would really do.  When it comes time to raise the event on the Containee, the following occurs:




  • Containee just fires its events, and since the WeakContainer is a target for one of these, its handler runs.


  • That handler first checks to see if Target (from WeakReference) is not null (meaning the target object is still alive)



    • If it is, it simply calls through that targets handler.


    • If it isn’t, then it removes itself from the sender’s handler list, allowing the WeakContainer itself to be collected.





This approach results in the following for the “single use of Containee” case, as in the first diagram above:



 



And the multiple use of Containee looks like:



 






Note that:




  • See code for all of this at the end of this post.


  • The Containee knows nothing about any of this.  The Container is responsible for setting up the “weak delegate” construct.


  • The WeakContainer class is a private nested class inside the Container class.  No need for anyone outside to know about it.


  • There’s likely room for generalizing this for multiple event signatures through the use of generics.  I didn’t want to confuse the issue with that for now.


  • The WeakContainer class (to which Containee has a strong reference) may stick around indefinitely, as they are only reaped when an event is raised after their target has been collected.  That is one downside here, but note that only the WeakContainer class itself (which is basically just a shell) is temporarily leaked.  Far, far better than leaking the Container itself.


  • There are a few other subtleties here, but the above is the basic idea.  See the code below for the exact usage.


Code





using System;


using System.Diagnostics;


 


namespace WeakDelegate


{


    // Containee needn’t know anything about weak references, etc. 


    // It just fires its events as normal.


    class Containee


    {


        public Containee(string id) { _id = id; }


        public event EventHandler MyEvent;


 


        public void DoIt()


        {


            if (MyEvent != null)


            {


                MyEvent(this, new EventArgs());


            }


        }


 


        public string ID { get { return _id; } }


 


        ~Containee()


        {


            Console.WriteLine(” Containee ‘{0}’ cleaned up”, ID);


        }


 


        private string _id;


    }


 


    // Container has a reference to Containee. But doesn’t want to


    // force Containee to have a strong reference to it.  Uses a


    // WeakContainer private nested class for this.


    class Container


    {


        public void Handler1(object sender, EventArgs args)


        {


            Console.WriteLine(


                ” Container.Handler1 called with Containee ‘{0}'”,


                _containee == null ? “<none>” : _containee.ID);


        }


 


        public Containee Containee


        {


            get


            {


                return _containee;


            }


 


            set


            {


                if (_weakContainer == null)


                {


                    _weakContainer = new WeakContainer(this);


                }


 


                // unsubscribe old


                if (_containee != null)


                {


                    _containee.MyEvent -= new


EventHandler(_weakContainer.Handler1);


                }


 


                // subscribe new


                _containee = value;


                if (_containee != null)


                {


                    _containee.MyEvent += new


EventHandler(_weakContainer.Handler1);


                }


            }


        }


 


        ~Container()


        {


            Console.WriteLine(” Container cleaned up”);


        }


 


        #region WeakContainer


 


        private class WeakContainer : WeakReference


        {


            public WeakContainer(Container target) : base(target) {}


 


            public void Handler1(object sender, EventArgs args)


            {


                Console.WriteLine(” WeakContainer.Handler1 called”);


                Container b = (Container)this.Target;


                if (b != null)


                {


                    b.Handler1(sender, args);


                }


                else


                {


                    Containee c = sender as Containee;


                    if (c != null)


                    {


                        c.MyEvent -= new EventHandler(this.Handler1);


                        Console.WriteLine(


” Removed WeakContainer handler”);


                    }


                }


            }


            ~WeakContainer()


            {


                Console.WriteLine(” WeakContainer cleaned up”);


            }


        }


 


        #endregion


 


        private Containee _containee;


        private WeakContainer _weakContainer = null;


    }


 


 


    class Class1


    {


        /// <summary>


        /// The main entry point for the application.


        /// </summary>


        [STAThread]


        static void Main(string[] args)


        {


            (new Class1()).Run();


        }


 


        private Containee containee1, containee2;


 


        private void InvokeMe()


        {


            Console.WriteLine(“Invoking”);


            if (containee1 != null) containee1.DoIt();


            if (containee2 != null) containee2.DoIt();


        }


 


        public void Run()


        {


            containee1 = new Containee(“FIRST”);


            containee2 = new Containee(“SECOND”);


            Container container = new Container();


            container.Containee = containee1;


 


            InvokeMe();


 


            Console.WriteLine(“Switching to containee2”);


            container.Containee = containee2;


           


            InvokeMe();


 


            Console.WriteLine(“Switching back to containee1”);


            container.Containee = containee1;


            InvokeMe();


 


            Console.WriteLine(“Setting container to null and GC’ing”);


            container = null;


            GC.Collect();


            GC.WaitForPendingFinalizers();


           


            InvokeMe();


           


            InvokeMe();


 


            Console.ReadLine();


        }


    }


}






Finally, note that in this particular chunk of code, the diagrams above aren’t quite complete.  The missing piece is that Container maintains a strong reference to WeakContainer.  But that’s fine, it doesn’t prevent Container from going away just because Containee is still alive.  Here’s the more accurate diagram:



 










Comments (37)

  1. IM says:

    This looks like an interesting article. I must be misunderstanding it on initial glance, because it reminds me of horrible COM circular reference stuff that presumably couldnt happen in .NET.

    So I’ll have to come back to it later for a proper look!

  2. Predrag Grkovic says:

    Great article, Greg. I was myself the "victim" of strong references made by long living event publishers. I wonder why events/handlers were not implemented as weak delegates by default – event publishers should not depend on its subscribers and the way subscribers handle the event. Although the pattern you’re suggesting is an improvement, it is still not a complete solution. The container still responds to events even after being selected for collection by garbage collector, when weak reference becomes the only reference to a container. Target property of WeakReference becomes null only after GC collected the object. That’s why you nulled it and immediately called GC.Collect() in your test application. I don’t know is it only me, but I expect the object that is selected for garbage collection but not collected yet, to be "dead" and not to respond to events it’s subscribed to. Weak references do not solve this problem, but at least, comparing to strong references, they allow for garbage collector to collect container object (event subscribers) when the time comes.

  3. For this particular problem, it would have been far simpler for the Transform, or better yet, a container for the Transform, to have a reference to the parent, and let that trickle up the tree.

    Here is how we do it in VG.net:

    – Each Element (graphical object) can optionally contain a transformation

    – The transformation is exposed via properties,

    such as Rotation

    – If you set a transforamtion property, internally a Transfomation struct is stored, and

    – Every time a transformation property is modified, a child element notifies it’s parent, and so on, on up the tree.

    What is wrong with this simple approach?

  4. Greg Schechter says:

    Predrag… you’re right in saying that the weak reference’s Target may not yet be null even though the Container no longer has any strong references to it.

    However, the reason I null’d it and called GC.Collect() in the test application was merely to test make it clear that the approach was working. I don’t expect this to be done in a real usage.

    The upshot is that the container needs to be resilient in the face of receiving event handler invocations even if there were no outstanding strong references to it. Typically, I don’t think this should be a problem.

    But, fundamentally, you’re definitely right that this isn’t the end-all, be-all solution. An approach that had a first-class notion of "weak delegates" would likely be the way to go.

  5. Greg Schechter says:

    Frank… the reason the approach you describe doesn’t work in Avalon is because Avalon allows many of its objects (in this case, Transforms) to be used from many different places (in this case, Visuals).

    Here’s the specific problem. If a Transform is in use in two separate Visuals (A and B), then that Transform would hold a reference to each of these Visuals. If the app lets go of all of its references to Visual A, it would rightly expect Visual A to be garbage collected. Problem is, though, that the Transform (which is still alive because Visual B still has a reference to it) still has a strong reference to Visual A.

    This is the gist of the second image in the Post above.

    One approach that would still allow all strong references to be used would be explicit disposal of Visual A, which would then actively remove any connections anyone else has to it. But that goes against the methodology of dynamic memory management and the garbage collector — where explicit disposal is discouraged because of the general uncertainty an app has as to when it’s OK to dispose of something.

    Hence, an approach that utilizes weak references in some fashion is necessary if we want to avoid the situation above where Visual B, just because it’s holding onto the same Transform that Visual B used, forces Visual B to stay alive.

  6. Xavier Musy says:

    variation on the themea and alternate solution

  7. Greg: In VG.net we just don’t allow the same hierarchy to be simultaneously displayed on two different controls — so the problem does not arise.

    Now, why you want to allow a Transform to be used in two different Visuals, I cannot understand. As soon as you allow multiple parents in a display hierarchy you get into all these nasty messes; that is why we avoided it. It would have also made it more difficult for us to cache optimization info (pixel bounds) in the leaves, since that info is per-control (per-Visual in your case).

    One side effect of the approach you are taking is that the connections between the child and parent start to become "fat" and heavy-weight. It seems to me that the connection between a Transform, a very low-level object, and its parent, should be as light as possible. Restricting children to a single parent achieves that goal — all you need is a simple object reference.

    If you are to allow multiple parents in a display hierarchy it is best to limit that to a small number of very high level classes, so everything remains scalable.

  8. Well here we are again .&amp;nbsp; For those of you who remember, I made a post a while back about events…

  9. meneame.net says:

    What follows may seem like a fairly obscure topic relating to lifetime management in the CLR, but for those who have hit upon this scenario (and they may not even know they have), this is a very helpful pattern to know about.

    In some of the development

  10. Interesting post over here from Greg Schechter about the idea of a "weak delegate". The CLR has support…

  11. This problem actually comes up pretty often so I thought I’d write a little article about it, and a couple

  12. You’ve been kicked (a good thing) – Trackback from DotNetKicks.com

  13. Há uns tempos atrás o colega Miguel Carvalho chamou-me a atenção para um bug que tinha encontrado num

  14. hot free myspace layouts codes generator iv

  15. &#160; There are numbers of blogs that folks wrote about memory leaks in Microsoft .Net Framework managed

  16. There are numbers of blogs that folks wrote about memory leaks in Microsoft .Net Framework managed code

  17. Event handling subscribe / unsubscribe to an event

  18. Bill Li says:

    Memory leaks are always headache of developers. Do .NET developers no longer bother to worry about memory

  19. Delay's Blog says:

    One of the nice things about developing on a platform that uses a garbage collecting memory manager (like

  20. It’s been a while since the last post was online. We have been very busy in working on one of the very

Skip to main content