Garbage collection when using anonymous delegates for event handling

后端 未结 4 1747
灰色年华
灰色年华 2020-11-27 18:10

UPDATE

I have combined various answers from here into a \'definitive\' answer on a new question.

Original question

相关标签:
4条回答
  • 2020-11-27 18:32

    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.

    0 讨论(0)
  • 2020-11-27 18:34

    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)

    0 讨论(0)
  • 2020-11-27 18:38

    Building further on Egor's answer, I wanted to try and build a version where I didn't have to determine in advance which event I want to attach to.

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

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

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

    0 讨论(0)
  • 2020-11-27 18:40

    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.

    0 讨论(0)
提交回复
热议问题