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

UPDATE

我已经将来自这里的各种答案综合成一个关于新问题的“确定性”答案。

原始问题

在我的代码中,我有一个事件发布者,它存在于应用程序的整个生命周期中(这里简化为必需品):

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

事实证明,代码中有一个地方,用户控件正在动态创建,添加和从表单中删除。 鉴于我对垃圾收集等方面的深入了解(即没有,直到昨天),我从来没有想过要在我身后清理,就像在绝大多数情况下一样,订户也一直活着应用程序的整个生命周期。

我已经与达斯汀坎贝尔的WeakEventHandler一起弄了一段时间,但它不适用于匿名代表(不适合我)。

反正有没有这个问题? 我真的很想避免在整个商店中复制粘贴锅炉代码。

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

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

(PPS:来自Rory的良好答案,但是如果任何人都能拿出与WeakEventHandler等价的方法来避免我必须记住明确UnLink / Dispose,那就太酷了......)

编辑我必须承认,我通过“回收”有问题的控件来解决这个问题。 然而,由于我使用的“关键”显然不是唯一的(啜泣),所以解决方法已经回到困扰我的地步。 我刚刚在这里发现了其他的链接(尝试了这一点 - 似乎有点太弱 - 即使目标还活着,GC也会清除代表,同样的问题,下面的答案),这里(迫使你修改发布者,并且不会真的不和匿名代表一起工作)和这里(Dustin Campbell引用的不完整)。

对我而言,我所寻找的东西在语义上可能是不可能的 - 闭包被设计为“即使在我离开后仍然存在”。

我找到了另一种解决方法,所以我会坚持这一点,等待来自众神的声音。


我知道这个问题是古老的,但是地狱 - 我找到了它,并且我认为别人也可以。 我正在尝试解决相关问题,并可能有一些洞察力。

你提到达斯汀坎贝尔的WeakEventHandler - 它确实无法通过设计与匿名方法一起工作。 当我意识到a)99%的情况下,我需要这样的东西时,他原来的解决方案会更安全,并且b)在少数情况下,我必须(注意:有而不是“因为lambdas更漂亮更简洁”),如果你有一点聪明,就可以使它工作。

你的例子看起来就像是一种一次性的情况,在这种情况下有点棘手可以产生一个相当简洁的解决方案。


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库似乎也有点沉重。


如果您保留对匿名代理的引用,然后在将表单从应该允许控制和匿名代表进行垃圾收集的表单中删除控件时将其删除。

所以像这样:

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

}

有关删除代理的示例,请参阅C#中的取消订阅匿名方法。


我最近根据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);
        }
    }
}

生成以下输出:

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 . . .

(请注意取消订阅( - =)不起作用)

链接地址: http://www.djcxy.com/p/51461.html

上一篇: Garbage collection when using anonymous delegates for event handling

下一篇: In Ext JS, how to attach events to dynamic html elements?