views:

368

answers:

5

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?

+5  A: 

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?

olliej
When the Elapsed event on the Timer occurs it happens on a new thread. Maybe that is what he is talking about. http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx
Joe
Sorry, I wasn't clear - the commented chunk is really more important than I gave it credit for. I'll try to update.
Damovisa
Also, what Joe K said - the Timer itself I assumed would run on a different thread. The Elapsed event certainly comes back on another thread.
Damovisa
I would expect that the Timer fires its event on it's own thread (though i doubt very much that that is guaranteed, i suspect the spec will allow threads to be reused for multiple timers), the timer should keep the Thinker instance live by keeping the delegate live. The issue is (i think) whether your thread will keep the Thinker live, and without knowing how you are launching your thread i'm unsure whether it would. I presume if you can reference it from your thread then you can, but like i said, i'm not sure.
olliej
Yes, based on Joe K's comment above (and the link he gave), it looks like the Timer uses a ThreadPool to raise Elapsed events. I think I'm just going to be safe and keep a class-level reference to the Thinker class from the main thread.
Damovisa
+2  A: 

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.

Joel Coehoorn
Something I'm looking at, but the Timer should still be running. Doesn't than mean the class is still alive?
Damovisa
The timer isn't running. When you start a timer, it creates an object on a different thread and tells windows to wake it up when the period has elapsed. There's nothing left on the current thread.
Joel Coehoorn
+4  A: 

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.

Kevin Montrose
A: 

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

Shafqat Ahmed
+2  A: 

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.

jrista
Thanks jrista - some good stuff in there. For reference, the background thread should finish almost immediately if it's going to finish at all. If I understand your answer correctly, the Timer should be preventing a premature garbage collection?
Damovisa
Correct, the Timer should be preventing premature garbage collection. (That responsibility is actually delegated to System.Threading.TimerBase, so its a few degrees removed, but the effect is the same.)
jrista