Garbage collection when using anonymous delegates for event handling
UPDATE
I have combined various answers from here into a 'definitive' answer on a new question.
Original question
In my code I have an event publisher, which exists for the whole lifetime of the application (here reduced to bare essentials):
public class Publisher
{
//ValueEventArgs<T> inherits from EventArgs
public event EventHandler<ValueEventArgs<bool>> EnabledChanged;
}
Because this publisher can be used all over the place, I was quite pleased with myself for creating this little helper class to avoid re-writing the handling code in all subscribers:
public static class Linker
{
public static void Link(Publisher publisher, Control subscriber)
{
publisher.EnabledChanged += (s, e) => subscriber.Enabled = e.Value;
}
//(Non-lambda version, if you're not comfortable with lambdas)
public static void Link(Publisher publisher, Control subscriber)
{
publisher.EnabledChanged +=
delegate(object sender, ValueEventArgs<bool> e)
{
subscriber.Enabled = e.Value;
};
}
}
It worked fine, until we started using it on smaller machines, when I started getting the occasional:
System.ComponentModel.Win32Exception
Not enough storage is available to process this command
As it turns out, there is one place in the code where subscribers controls are being dynamically created, added and removed from a form. Given my advanced understanding of garbage collection etc (ie none, until yesterday), I never thought to clear up behind me, as in the vast majority of cases, the subscribers also live for the lifetime of the application.
I've fiddled around a while with Dustin Campbell's WeakEventHandler, but it doesn't work with anonymous delegates (not for me anyway).
Is there anyway out of this problem? I really would like to avoid having to copy-paste boiler-plate code all over the shop.
(Oh, and don't bother with asking me WHY we are creating and destroying controls all the time, it wasn't my design decision...)
(PS: It's a winforms application, but we've upgraded to VS2008 and .Net 3.5, should I consider using the Weak Event pattern?)
(PPS: Good answer from Rory, but if anyone can come up with an equivalent to the WeakEventHandler which avoids me having to remember to explicitly UnLink/Dispose, that would be cool...)
EDIT I must admit that I worked around this problem by "recycling" the controls in question. However the workaround has come back to haunt me as the 'key' I was using is apparently non-unique (sob). I've just discovered other links here (tried this - seems to be a bit too weak - GC clears delegates even if target is still alive, same problem with s,oɔɯǝɹ answer below), here (forces you to modify publisher, and doesn't really work with anonymous delegates) and here (cited-as-incomplete by Dustin Campbell).
It occurs to me that what I'm looking for may be semantically impossible - closures are designed to 'hang around even after I'm gone'.
I've found another workaround, so I'll stick with that, pending a voice from the gods.
I know that this question is ancient, but hell - I found it, and I figure that others might as well. I'm trying to resolve a related issue, and might have some insight.
You mentioned Dustin Campbell's WeakEventHandler - it indeed cannot work with anonymous methods by design. I was trying to fiddle something together that would, when I realized that a) in 99% of cases I'd need something like this his original solution would be safer, and b) in those few cases where I have to (note: have to, not "want to because lambdas are so much prettier and concise") it's possible to make it work if you get a little clever.
Your example seems like exactly the kind of one-off case where getting a little tricky can result in a fairly concise solution.
public static class Linker {
public static void Link(Publisher publisher, Control subscriber) {
// anonymous method references the subscriber only through weak
// references,so its existance doesn't interfere with garbage collection
var subscriber_weak_ref = new WeakReference(subscriber);
// this instance variable will stay in memory as long as the anonymous
// method holds a reference to it we declare and initialize it to
// reserve the memory (also, compiler complains about uninitialized
// variable otherwise)
EventHandler<ValueEventArgs<bool>> handler = null;
// when the handler is created it will grab references to the local
// variables used within, keeping them in memory after the function
// scope ends
handler = delegate(object sender, ValueEventArgs<bool> e) {
var subscriber_strong_ref = subscriber_weak_ref.Target as Control;
if (subscriber_strong_ref != null)
subscriber_strong_ref.Enabled = e.Value;
else {
// unsubscribing the delegate from within itself is risky, but
// because only one instance exists and nobody else has a
// reference to it we can do this
((Publisher)sender).EnabledChanged -= handler;
// by assigning the original instance variable pointer to null
// we make sure that nothing else references the anonymous
// method and it can be collected. After this, the weak
// reference and the handler pointer itselfwill be eligible for
// collection as well.
handler = null;
}
};
publisher.EnabledChanged += handler;
}
}
The WPF Weak Event pattern is rumored to come with a lot of overhead, so in this particular situation I wouldn't use it. Furthermore, referencing the core WPF library in a WinForm app seems a little heavy as well.
If you retain a reference to the anonymous delegate and then remove it when the controls are removed from the form that should allow both the controls and the anonymous delegates to be garbage collected.
So something like this:
public static class Linker
{
//(Non-lambda version, I'm not comfortable with lambdas:)
public static EventHandler<ValueEventArgs<bool>> Link(Publisher publisher, Control subscriber)
{
EventHandler<ValueEventArgs<bool>> handler = delegate(object sender, ValueEventArgs<bool> e)
{
subscriber.Enabled = e.Value;
};
publisher.EnabledChanged += handler;
return handler;
}
public static void UnLink(Publisher publisher, EventHandler<ValueEventArgs<bool>> handler)
{
publisher.EnabledChanged -= handler;
}
}
See Unsubscribe anonymous method in C# for an example of removing delegates.
Some sample code i made recently, based on WeakReference:
// strongly typed weak reference
public class WeakReference<T> : WeakReference
where T : class
{
public WeakReference(T target)
: base(target)
{ }
public WeakReference(T target, bool trackResurrection)
: base(target, trackResurrection)
{ }
public new T Target
{
get { return base.Target as T; }
set { base.Target = value; }
}
}
// weak referenced generic event handler
public class WeakEventHandler<TEventArgs> : WeakReference<EventHandler<TEventArgs>>
where TEventArgs : EventArgs
{
public WeakEventHandler(EventHandler<TEventArgs> target)
: base(target)
{ }
protected void Invoke(object sender, TEventArgs e)
{
if (Target != null)
{
Target(sender, e);
}
}
public static implicit operator EventHandler<TEventArgs>(WeakEventHandler<TEventArgs> weakEventHandler)
{
if (weakEventHandler != null)
{
if (weakEventHandler.IsAlive)
{
return weakEventHandler.Invoke;
}
}
return null;
}
}
// weak reference common event handler
public class WeakEventHandler : WeakReference<EventHandler>
{
public WeakEventHandler(EventHandler target)
: base(target)
{ }
protected void Invoke(object sender, EventArgs e)
{
if (Target != null)
{
Target(sender, e);
}
}
public static implicit operator EventHandler(WeakEventHandler weakEventHandler)
{
if (weakEventHandler != null)
{
if (weakEventHandler.IsAlive)
{
return weakEventHandler.Invoke;
}
}
return null;
}
}
// observable class, fires events
public class Observable
{
public Observable() { Console.WriteLine("new Observable()"); }
~Observable() { Console.WriteLine("~Observable()"); }
public event EventHandler OnChange;
protected virtual void DoOnChange()
{
EventHandler handler = OnChange;
if (handler != null)
{
Console.WriteLine("DoOnChange()");
handler(this, EventArgs.Empty);
}
}
public void Change()
{
DoOnChange();
}
}
// observer, event listener
public class Observer
{
public Observer() { Console.WriteLine("new Observer()"); }
~Observer() { Console.WriteLine("~Observer()"); }
public void OnChange(object sender, EventArgs e)
{
Console.WriteLine("-> Observer.OnChange({0}, {1})", sender, e);
}
}
// sample usage and test code
public static class Program
{
static void Main()
{
Observable subject = new Observable();
Observer watcher = new Observer();
Console.WriteLine("subscribe new WeakEventHandler()n");
subject.OnChange += new WeakEventHandler(watcher.OnChange);
subject.Change();
Console.WriteLine("nObserver = null, GC");
watcher = null;
GC.Collect(0, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
subject.Change();
if (Debugger.IsAttached)
{
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
}
Generates the following output:
new Observable()
new Observer()
subscribe new WeakEventHandler()
DoOnChange()
-> Observer.OnChange(ConsoleApplication4.Observable, System.EventArgs)
Observer = null, GC
~Observer()
DoOnChange()
~Observable()
Press any key to continue . . .
(Note that unsubscribing (-=) doesn't work)
链接地址: http://www.djcxy.com/p/51462.html上一篇: .NET中的弱事件?
下一篇: 使用匿名代理进行事件处理时的垃圾回收