Why event handlers prevent garbage collector from occurring
Asked Answered
A

2

9

I have this piece of code

public class Publisher
{
    public event EventHandler SomeEvent;
}

public class Subscriber
{
    public static int Count;

    public Subscriber(Publisher publisher)
    {
        publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
    }

    ~Subscriber()
    {
        Subscriber.Count++;
    }

    private void publisher_SomeEvent(object sender, EventArgs e)
    {
        // TODO
    }
}

In the Main method of my application I have

static void Main(string[] args)
{
    Publisher publisher = new Publisher();

    for (int i = 0; i < 10; i++)
    {
        Subscriber subscriber = new Subscriber(publisher);
        subscriber = null;
    }

    GC.Collect();
    GC.WaitForPendingFinalizers();

    Console.WriteLine(Subscriber.Count.ToString());
}

If I run this, I will have 0 as output. If I remove event subscriptions from the code, I will got the expecting result – which is 10.

When GC.Collect() is being called, gc is being forced to start garbage collection. Because Subscriber has Finalize defined in it, GC will suspend collection until finalizequeue is empty – that is after all Subscription instances will call its Finalize() methods ( Please correct me if my assumptions are wrong). At the next line GC.WaitForPendingFinalizers() is called which will effectively suspend execution until finalizer queue is empty. Now, because we have 0 as output I believe Finalize() is not being called, which makes me believe that GC didn’t mark subscriber instances to be collected, thus Finalizer() methods are not being called.

So I have 2 questions

  1. Is my assumption right and event subscription prevents GC to mark subscriber instances to be collected?
  2. If so, it is because publisher holds reference to subscriber? (Garbage collector and event handlers)

My only guess is that since there are 10 instances of Subscriber that are referencing to the same publisher instance, when GC collection occurs, it sees that there are other references to publisher, thus it can’t be collected, and as a result all subscription instances alongside with publisher are being moved to the next generation, so garbage collection doesn’t occur nor Finalize() is being called at the time code execution reaches to Console.WriteLine(Subscriber.Count.ToString())

Am I right or am I missing something here?

Airless answered 18/6, 2013 at 8:54 Comment(1)
Think about it this way: when Publisher.SomeEvent delegate is being called it will execute whatever is subscribed to it at the moment of the call. This means that the subscriber instances has to be alive while the subscription is active. That's why Publisher has to keep a reference to all the subscribers. Since subscribers are reachable GC can't collect them.Technology
T
4

You are mis-identifying what is really going on, a very common trap in C#. You'll need to run the Release build of your test program and run it without the debugger (press Ctrl+F5). The way it will run on your user's machine. And now notice that it completely doesn't matter anymore whether or not you subscribe the event, you will always get 10.

The issue is that, when you use a debugger, the publisher object doesn't get collected. I explained the reason for that in detail in this answer.

Expanding a bit on that, you have circular references here. The Subscriber objects reference the Publisher object. And the Publisher object has references to the Subscriber objects. Circular references are not sufficient to keep objects alive. And thank goodness for that, garbage collection would not be very effective if that was the case. The publisher object must be referenced elsewhere to stay alive, the local variable isn't good enough.

Truant answered 18/6, 2013 at 13:21 Comment(4)
Thanks for the answer! Indeed the output is 10 when I am running Release build by hitting ctrl + f5.Airless
"The Subscriber objects reference the Publisher object." Really?Millennium
I guess that would mean that as long as the publisher object continues to be referenced, it's pretty much guaranteed to keep the subscriber ones alive?Vaud
Sure, a publisher that outlives the subscribers is not healthy. A big issue in WPF btw, they came up with the "weak event pattern". Not a universal solution, it burns a lot of cycles to discover dead subscribers. Okay for UI related events since UI runs at human speed, milliseconds. It is not uncommon for a programmer to reach for IDisposable to get the event unsubscribed, that's not correct either.Truant
M
3

The answer to both questions is Yes.

Millennium answered 18/6, 2013 at 8:57 Comment(3)
Thanks for confimation, do you think that assumption in the last paragraph is also correct?Airless
@michaelmoore The subscribers don't hold references to the publisher. It's the local variable publisher in Main which prevents the Publisher instance and in turn the 10 Subscriber instances from being collected.Millennium
You haven't explained why publisher isn't getting collected. It is getting collected, just not when the OP tried to run his code.Truant

© 2022 - 2024 — McMap. All rights reserved.