C# 使用匿名委托进行事件处理时的垃圾收集
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/371109/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Garbage collection when using anonymous delegates for event handling
提问by Benjol
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 (i.e. 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).
我已经在Dustin Campbell 的 WeakEventHandler 上摆弄了一段时间,但它不适用于匿名委托(反正不适合我)。
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?)
(PS:这是一个winforms应用程序,但我们已经升级到VS2008和.Net 3.5,我应该考虑使用弱事件模式吗?)
(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...)
(PPS:来自 Rory 的好回答,但如果有人能想出一个相当于 WeakEventHandler 的方法,这样我就不必记住明确地取消链接/处理,那会很酷......)
EDITI 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 tooweak - GC clears delegates even if target is still alive, same problem with s,o???? answerbelow), here(forces you to modify publisher, and doesn't really work with anonymous delegates) and here(cited-as-incomplete by Dustin Campbell).
编辑我必须承认我通过“回收”有问题的控件解决了这个问题。然而,解决方法又回来困扰我,因为我使用的“钥匙”显然不是唯一的(抽泣)。我刚刚发现的其他环节在这里(试过这-似乎有点过于软弱- GC清除代表即使目标还活着,同样的问题,S,O ????回答如下图),这里(迫使你修改出版商,并没有真正与匿名代表一起工作)和这里(达斯汀坎贝尔引用为不完整)。
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.
采纳答案by Egor
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.
您提到了 Dustin Campbell 的 WeakEventHandler - 它确实无法通过设计与匿名方法一起使用。我试图一起摆弄一些东西,当我意识到 a) 在 99% 的情况下我需要这样的东西他原来的解决方案会更安全,b) 在我必须的少数情况下(注意:有到,而不是“因为 lambdas 更漂亮和简洁而想要”),如果你有点聪明,就可以让它工作。
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.
传闻 WPF 弱事件模式会带来很多开销,因此在这种特殊情况下我不会使用它。此外,在 WinForm 应用程序中引用核心 WPF 库似乎也有点繁重。
回答by Rory
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.
回答by o????
Some sample code i made recently, based on WeakReference:
我最近基于 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)
(请注意,取消订阅 (-=) 不起作用)
回答by Benjol
Building further on Egor'sanswer, I wanted to try and build a version where I didn't have to determine in advance whichevent I want to attach to.
在Egor 的回答的基础上进一步构建,我想尝试构建一个版本,我不必提前确定要附加到哪个事件。
I've only managed to make it work with generic event handlers: for 'standard' event handlers (eg. FormClosingEventHandler), it's a bit tricky, because you can't have a type constraint where T : delegate
(unless your name ends with Pony).
我只设法让它与通用事件处理程序一起工作:对于“标准”事件处理程序(例如 FormClosingEventHandler),这有点棘手,因为您不能有类型约束where T : delegate
(除非您的名字以Pony结尾)。
private static void SetAnyGenericHandler<S, T>(
Action<EventHandler<T>> add, //to add event listener to publisher
Action<EventHandler<T>> remove, //to remove event listener from publisher
S subscriber, //ref to subscriber (to pass to consume)
Action<S, T> consume) //called when event is raised*
where T : EventArgs
where S : class
{
var subscriber_weak_ref = new WeakReference(subscriber);
EventHandler<T> handler = null;
handler = delegate(object sender, T e)
{
var subscriber_strong_ref = subscriber_weak_ref.Target as S;
if(subscriber_strong_ref != null)
{
Console.WriteLine("New event received by subscriber");
consume(subscriber_strong_ref, e);
}
else
{
remove(handler);
handler = null;
}
};
add(handler);
}
(*I did try EventHandler<T> consume
here, but the calling code gets ugly because you have to cast s to Subscriber in the consume lambda.)
(*我确实EventHandler<T> consume
在这里尝试过,但是调用代码变得难看,因为您必须在消耗 lambda 中将 s 强制转换为 Subscriber。)
Calling code example, taken from example above:
调用代码示例,取自上面的示例:
SetAnyGenericHandler(
h => publisher.EnabledChanged += h,
h => publisher.EnabledChanged -= h,
subscriber,
(Subscriber s, ValueEventArgs<bool> e) => s.Enabled = e.Value);
Or, if you prefer
或者,如果你喜欢
SetAnyGenericHandler<Subscriber, ValueEventArgs<bool>>(
h => publisher.EnabledChanged += h,
h => publisher.EnabledChanged -= h,
subscriber,
(s, e) => s.Enabled = e.Value);
It would be nice to be able to pass in the Event as just one parameter, but you can't access add/remove from an event any more than you can access get/set from a property (without doing yucky reflexion stuff, I think).
能够仅将 Event 作为一个参数传递会很好,但是您无法从事件中访问 add/remove,就像您无法从属性中访问 get/set 一样(我认为不做令人讨厌的反射操作) )。