GC Question

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;
}
 
J

Jon Skeet [C# MVP]

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

Willy Denoyette [MVP]

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

Cool Guy

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.");
}
}
 
J

Jon Skeet [C# MVP]

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

Willy Denoyette [MVP]

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

Willy Denoyette [MVP]

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

Cool Guy

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

Cool Guy

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

Jon Skeet [C# MVP]

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

Jon Skeet [C# MVP]

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

Jon Skeet [C# MVP]

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

Willy Denoyette [MVP]

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

Jon Skeet [C# MVP]

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

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top