GC Question

  • Thread starter Thread starter Cool Guy
  • Start date Start date
C

Cool Guy

In the following code, is it possible that foo be garbage collected (and
therefore foo_Finished() never gets entered? I'd imagine so, because there
are no references to foo after Main exits. However, I can't make this
happen even with calls to GC.Collect().

using System;
using System.Threading;

delegate void ParameterlessDelegate();

class Test
{
static void Main()
{
Foo foo = new Foo();
foo.Finished += new ParameterlessDelegate(foo_Finished);
foo.Start();
}

static void foo_Finished()
{
Console.WriteLine("Finished.");
}
}

class Foo
{
public void Start()
{
new Thread(new ThreadStart(ThreadJob)).Start();
}

void ThreadJob()
{
Console.WriteLine("Working...");
Thread.Sleep(2000);
Finished();
}

public event ParameterlessDelegate Finished;
}
 
Cool Guy said:
In the following code, is it possible that foo be garbage collected (and
therefore foo_Finished() never gets entered? I'd imagine so, because there
are no references to foo after Main exits. However, I can't make this
happen even with calls to GC.Collect().

Yes, there's a reference, due to the fact that the other thread is
running in an instance method, which (sort of) makes "this" an instance
method.

I say "sort of" advisedly - there are certain situations when an object
*can* be garbage collected when running in an instance method, as
described here:
http://blogs.msdn.com/cbrumme/archive/2003/04/19/51365.aspx

For the most part, however, you don't need to worry about that - if a
thread is running, and it has a reference to an object (including
"this") and it's going to *use* the object, the object won't be garbage
collected.
 
Your program terminates even before your thread had a chance to run.
You should at some point rendez-vous after foo.Start(), in your case you can
wait for the thread procedure to finish by calling Thread.Join(), but
normaly you should continue to execute code on the calling thread.

change your code into ...
Thread t = new Thread(new ThreadStart(ThreadJob)).Start();
t.Join();


Willy.
 
Jon Skeet said:
Yes, there's a reference, due to the fact that the other thread is
running in an instance method, which (sort of) makes "this" an instance
method.

Hmm... I don't understand that last part about making "this" an instance
method.
For the most part, however, you don't need to worry about that - if a
thread is running, and it has a reference to an object (including
"this")

So, when I pass ThreadJob to the thread, I'm also passing a reference to
the object, right?
and it's going to *use* the object, the object won't be garbage
collected.

So in the modified version below, is it guaranteed that "Finished." be
displayed? (This no longer uses the object.)

using System;
using System.Threading;

delegate void ParameterlessDelegate();

class Test
{
static void Main()
{
Foo foo = new Foo();
foo.Start();
}
}

class Foo
{
public void Start()
{
new Thread(new ThreadStart(ThreadJob)).Start();
}

void ThreadJob()
{
Console.WriteLine("Working...");
Thread.Sleep(2000);
Console.WriteLine("Finished.");
}
}
 
Willy Denoyette said:
Your program terminates even before your thread had a chance to run.

I don't think so. It calls Thread.Start(), which sets the (other)
thread status to "running" - at which point the program won't terminate
until that thread has stopped, as it's not a background thread.

Why would the program terminate?

(Try running it - it certainly prints up "Working..." and "Finished."
for me.)
 
Forget my previous post it was complete nonsense, your secondary thread is
not a background thread so main will not exit before the thread procedure
returns.
The result is that foo remains a valid reference (not be "nulled") until the
process exits (orderly shutdown).

Willy.
 
Jon Skeet said:
I don't think so. It calls Thread.Start(), which sets the (other)
thread status to "running" - at which point the program won't terminate
until that thread has stopped, as it's not a background thread.

Why would the program terminate?

(Try running it - it certainly prints up "Working..." and "Finished."
for me.)

My bad, see my other reply.

Willy.
 
Cool Guy said:
So, when I pass ThreadJob to the thread, I'm also passing a reference to
the object, right?

Hmm... then again, this is probably irrelevant, since Thread doesn't appear
to hold a reference to the ThreadStart anyway (from looking at Thread in
Reflector).
 
I wrote:

[snip]

Okay, I think I understand this now. If I'm wrong I'd appreciate anyone
correcting me.

In my first example, since the code in ThreadJob accesses *this* (when it
raises the Finished event), that object is still referenced by the app and
therefore it doesn't get garbage collected.

In my second example (where the code in ThreadJob doesn't access any
state), that object could be garbage collected before ThreadJob has
finished executing -- but that doesn't matter, since I never again access
the object anyway.
 
Cool Guy said:
Hmm... I don't understand that last part about making "this" an instance
method.

Sorry, typo there - I meant it makes "this" a live reference.
So, when I pass ThreadJob to the thread, I'm also passing a reference to
the object, right?

Yes - the delegate has a reference to its target.
So in the modified version below, is it guaranteed that "Finished." be
displayed? (This no longer uses the object.)

Yup.
 
Willy Denoyette said:
Forget my previous post it was complete nonsense, your secondary thread is
not a background thread so main will not exit before the thread procedure
returns.
The result is that foo remains a valid reference (not be "nulled") until the
process exits (orderly shutdown).

It's not that foo is a live reference (you could set foo to null after
calling foo.Start()) - but that the other thread has a reference to the
same object. Maybe that's what you meant - not sure.
 
Cool Guy said:
I wrote:

[snip]

Okay, I think I understand this now. If I'm wrong I'd appreciate anyone
correcting me.

In my first example, since the code in ThreadJob accesses *this* (when it
raises the Finished event), that object is still referenced by the app and
therefore it doesn't get garbage collected.
Exactly.

In my second example (where the code in ThreadJob doesn't access any
state), that object could be garbage collected before ThreadJob has
finished executing -- but that doesn't matter, since I never again access
the object anyway.

Spot on.
 
Jon Skeet said:
It's not that foo is a live reference (you could set foo to null after
calling foo.Start()) - but that the other thread has a reference to the
same object. Maybe that's what you meant - not sure.

That's exactly what I meant, the thread holds a reference to the Foo object
on it's call stack (in this case the this pointer passed by the ThreadStart
delegate). Note that it's not necessary to set foo to null after the call to
calling foo.Start() returns, this is taken care of by the JIT.

Willy.
 
Willy Denoyette said:
That's exactly what I meant, the thread holds a reference to the Foo object
on it's call stack (in this case the this pointer passed by the ThreadStart
delegate). Note that it's not necessary to set foo to null after the call to
calling foo.Start() returns, this is taken care of by the JIT.

Absolutely. I thought that would almost certainly be what you meant -
just thought it was worth clarifying :)
 
Back
Top