Will .Net Garbage Collect an object that's not

2019-01-25 14:51发布

问题:

I have the following code (cut down for readability):

Main Class:

public StartProcess()
{
    Thinker th = new Thinker();
    th.DoneThinking += new Thinker.ProcessingFinished(ThinkerFinished);
    th.StartThinking();
}

void ThinkerFinished()
{
    Console.WriteLine("Thinker finished");
}

Thinker Class:

public class Thinker
{
    private System.Timers.Timer t;

    public delegate void ProcessingFinished();
    public event ProcessingFinished DoneThinking;

    BackgroundWorker backgroundThread;

    public Thinker() { }

    public StartThinking()
    {
        t = new System.Timers.Timer(5000);    // 5 second timer
        t.AutoReset = false;
        t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);
        t.Start();

        // start a background thread to do the thinking
        backgroundThread = new BackgroundWorker();
        backgroundThread.DoWork += new DoWorkEventHandler(BgThread_DoWork);
        backgroundThread.RunWorkerAsync();
    }

    void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        DoneThinking();
    }

    BgThread_DoWork(object sender, DoWorkEventArgs e)
    {
        // work in here should go for much less than 5 seconds
        // it will die if it doesn't

        t.Stop();
        DoneThinking();
    }
}

What I originally expected to happen was that the event handler in the main class would prevent the Thinker from being garbage collected.

Apparently this isn't the case.

I'm now wondering whether garbage collection will occur regardless of whether this thread is "busy" or not. In other words, is there a chance it will be garbage collected before the 5 second timeout has expired?

To put it another way, is it possible for the garbage collector to collect my Thinker before it's finished processing?

回答1:

No, a thread is considered live as long as it is referenced, and any thread that is running is considered to be referenced (IIRC a running thread registers its stack as a GC root, and that stack will reference the thread).

That said i'm looking at your example and i don't understand where you believe a thread is being spawned?



回答2:

No, a running thread's stack acts as a root for GC purposes. That stack will live as long as the Thread is running, so the Thread itself won't be collected as long its running.

Here's an article that mentions (among other things) what the roots are for GC purposes. To save some time, GC roots are global objects, static objects, all reference on all thread stacks, and all CPU registers containing references.



回答3:

Your question is a little difficult to answer. Like Joel, as far as I can tell, you have nothing on the stack referencing your timer, which itself is the only thing referencing the thread. Given that, one would expect the Thinker instance would be collected.

I was curious about this, and needed a more concrete explanation of what might happen, so I dug into Reflector a bit. As it turns out, System.Timers.Timer ultimately creates a System.Threading.Timer, which internally creates an instance of TimerBase, an internal class. TimerBase derives from CriticalFinalizerObject, which is a system type that ensures that all code in a Constrained Execution Region (CER) will execute before the implementing class is fully finalized and discarded by the GC. TimerBase is also IDisposable, and its dispose method loops and spinwaits until a lock is released. At this point, I started running into external code, so I am not exactly sure how the lock is initialized or released.

However, based on how the TimerBase class is written, the fact that it derives from CriticalFinalizerObject, and the fact that its dispose spinwaits until a lock is released, I think its safe to say that a thread that is not referenced by anything will not be finalized until that code is done executing. That said...it is important to note that it quite likely will be processed by the GC...quite possibly more than once, as finalization can greatly lengthen the process of collection on finalized objects. For those that are CriticalFinalizerObjects, the finalization process could take even longer if there is actively executing code that the CER is ensuring will fully execute.

That could mean you have exactly the opposite problem if your Thinkers take a while to execute. Rather than those objects being collected prematurely, they will go into a lengthy finalization, and anything they reference ending up in gen2, and living for quite some time until the GC is finally able to fully collect them.



回答4:

If I'm reading this right (and I could be way off here), it can be collected because it's not currently doing anything.

If you had local variables in your start method, and that method was still active, those variables would still be "in scope" on the stack and provide a root for your thread. But the only variable you use is your private timer, and since that is rooted with the thread itself and the thread has nothing on the stack, there's nothing left to keep it alive.



回答5:

I agree and disagree, If the reference to the thread object is lost the thread will be terminated and garbage collected. In your case it might not be as such because it is not directly using threads and is using timer. But if you had called a method in a thread and the thread reference was lost with the end of the method then it would be collected by GC