一尘不染

使用匿名委托进行事件处理时的垃圾回收

c#

更新

我将这里的各种答案组合成一个新问题的“确定性”答案。

原始问题

在我的代码中,我有一个事件发布者,该事件发布者在应用程序的整个生命周期中都存在(这里简化为基本要素):

public class Publisher
{
    //ValueEventArgs<T> inherits from EventArgs
    public event EventHandler<ValueEventArgs<bool>> EnabledChanged; 
}

因为可以在所有地方使用此发布者,所以我对自己创建这个小帮助程序类以避免在所有订阅者中重写处理代码感到非常满意:

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;
             };
    }
}

它运行良好,直到我们开始在较小的机器上使用它时,偶尔我会发现:

System.ComponentModel.Win32Exception
Not enough storage is available to process this command

事实证明,在代码中有一个地方可以动态创建订户控件,将其添加到表单中或从表单中删除。鉴于我对垃圾回收等有深入的了解(即直到昨天还没有),所以我从来没有想过要在我身后清理,因为在大多数情况下,订户还可以在应用程序的生命期内生存。

我在Dustin
Campbell的WeakEventHandler上摆
了一段时间,但是它
不适用于匿名代表 (无论如何对我来说不是)。

反正有这个问题吗?我真的想避免在整个商店中复制粘贴样板代码。

(哦,不要问我为什么我们一直在创建和销毁控件,这不是我的设计决定…)

(PS:这是一个winforms应用程序,但是我们已经升级到VS2008和.Net
3.5,我应该考虑使用弱事件模式吗?)

(PPS:来自Rory的很好的回答,但是如果任何人都可以提出与WeakEventHandler等效的方法,这避免了我不得不记住显式地取消链接/处置,那将很酷…)

编辑
我必须承认我通过“回收”有问题的控件来解决此问题。但是,由于我正在使用的“密钥”显然是不唯一的(sob),因此变通方法再次困扰了我。我刚刚发现的其他环节在这里(试过这-
似乎有点 过于 软弱-
GC清除代表即使目标还活着,同样的问题S,oɔɯǝɹ答案如下图),这里(逼着你修改发布,并没有按并没有真正与匿名代表合作)和此处(达斯汀·坎贝尔(Dustin Campbell)引用为不完整)。

在我看来,我正在寻找的东西在语义上可能是不可能的-闭包被设计为“即使我走了也仍然四处徘徊”。

我找到了另一个解决方法,因此我会坚持下去,等待众神声音


阅读 433

收藏
2020-05-19

共1个答案

一尘不染

我知道这个问题是古老的,但是地狱-我找到了,我认为其他人也可能如此。我正在尝试解决一个相关问题,可能会有一些见识。

您提到了达斯汀·坎贝尔(Dustin Campbell)的WeakEventHandler-
实际上,它确实无法与匿名方法一起使用。当我意识到a)在99%的情况下我需要这样的东西时,我正在尝试摆弄一些东西,他最初的解决方案会更安全,并且b)在少数情况下我必须(注意:拥有)而不是“因为lambda太漂亮和简洁了”),如果您有点聪明,就有可能使它起作用。

您的示例似乎完全是一次性的情况,其中有些棘手的问题可能会导致一个相当简洁的解决方案。

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;
    }
}

据传WPF弱事件模式会带来很多开销,因此在这种特殊情况下,我将不使用它。此外,在WinForm应用程序中引用核心WPF库似乎也很繁重。

2020-05-19