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
Anonymous
May 27, 2004
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!Anonymous
June 02, 2004
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.Anonymous
June 03, 2004
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?Anonymous
June 03, 2004
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.Anonymous
June 03, 2004
The comment has been removedAnonymous
June 06, 2004
variation on the themea and alternate solutionAnonymous
June 23, 2004
The comment has been removedAnonymous
April 11, 2005
Well here we are again .&nbsp; For those of you who remember, I made a post a while back about events...Anonymous
February 16, 2006
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 developmentAnonymous
October 02, 2006
Interesting post over here from Greg Schechter about the idea of a "weak delegate". The CLR has support...Anonymous
December 14, 2006
PingBack from http://bryanallott.net/journal/2006/11/07/delegate-gotcha/Anonymous
January 09, 2007
This problem actually comes up pretty often so I thought I'd write a little article about it, and a coupleAnonymous
January 11, 2007
The comment has been removedAnonymous
February 12, 2007
You've been kicked (a good thing) - Trackback from DotNetKicks.comAnonymous
February 15, 2007
PingBack from http://www.sral.org/2007/02/16/links-for-2007-02-16/Anonymous
March 02, 2007
PingBack from http://www.wesay.org/blogs/2006/08/19/34/Anonymous
April 22, 2007
Há uns tempos atrás o colega Miguel Carvalho chamou-me a atenção para um bug que tinha encontrado numAnonymous
September 17, 2007
The comment has been removedAnonymous
October 11, 2007
PingBack from http://www.paulstovell.net/blog/index.php/wpf-binding-bug-leads-to-possible-memory-issues/Anonymous
February 04, 2008
  There are numbers of blogs that folks wrote about memory leaks in Microsoft .Net Framework managedAnonymous
February 04, 2008
There are numbers of blogs that folks wrote about memory leaks in Microsoft .Net Framework managed codeAnonymous
February 04, 2008
PingBack from http://msdnrss.thecoderblogs.com/2008/02/04/finding-memory-leaks-in-wpf-based-applications/Anonymous
February 13, 2008
PingBack from http://robburke.net/2008/02/13/are-your-clr-based-apps-leaking/Anonymous
March 26, 2008
Event handling subscribe / unsubscribe to an eventAnonymous
February 10, 2009
Memory leaks are always headache of developers. Do .NET developers no longer bother to worry about memoryAnonymous
March 09, 2009
One of the nice things about developing on a platform that uses a garbage collecting memory manager (likeAnonymous
March 21, 2009
It's been a while since the last post was online. We have been very busy in working on one of the veryAnonymous
April 06, 2009
PingBack from http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/Anonymous
May 31, 2009
PingBack from http://indoorgrillsrecipes.info/story.php?id=4371Anonymous
June 07, 2009
PingBack from http://greenteafatburner.info/story.php?id=3126