Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does ParameterizedThreadStart guarantee that the object instance won't be garbage collected?

I've been playing around with the following piece of code:

class RunMeBaby
{
    public void Start()
    {
        while (true)
        {
            Console.WriteLine("I'm " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1000);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        RunMeBaby r = new RunMeBaby();
        Thread t = new Thread(r.Start);  // ParameterizedThreadStart delegate
        r = null;
        GC.Collect(GC.MaxGeneration);
        t.Start();

        r = new RunMeBaby();
        t = new Thread(() => r.Start()); // ThreadStart delegate
        t.Start();
        //Thread.Sleep(1000);
        r = null;
    }
}

While the first part of main is executed without a hitch, the second part fails when I comment the call to the Thread.Sleep() method, I get a null exception.

My understanding is that the lambda expression being lazily evaluated, it can happen that the new thread isn't started fast enough and the main one sets r to null first. Now I put this "second part" in a static method with r having a local scope, and the problem disappeared. But I wonder whether or not the problem is hidden by the thread scheduler in that particular case, maybe on a different machine with a different workload it could still occur. Or is there something about the lambda expression that guarantees that even though r falls out of scope, as long as it hasn't been set to null, it is still referenced somehow.

And ultimately I wonder whether I should consider using the ParameterizedThreadStart delegate as much as possible or stick to the lambdas given I respect certain conditions to keep them valid.

like image 968
To마SE Avatar asked Feb 20 '23 17:02

To마SE


2 Answers

Before we talk about garbage collection, first let's understand the code you have written.

There is a huge difference between:

new Thread(r.Start)

which creates a delegate for the Start method on the current value of r, i.e.

new Thread(new ThreadStart(r.Start)) // identical to new Thread(r.Start)

in either of the above, r is evaluated now, so later changes to another instance (or null) won't affect it. Contrast with:

new Thread(() => r.Start())

this is an anonymous method with captures the variable r, i.e. r is only evaluated when the anonymous method is invoked, i.e. when the second thread is running. Consequently, yes: if you change the value of r you could very well get a different result (or an error if you change it to null).

Parameterised thread start would work too:

new Thread(state => ((RunMeBaby)state).Start(), r);

which passes the current value of r as the parameter value, so that is now fixed; when the delegate is invoked, state gets the value that was (previously) in r at the time, so you can cast that to the appropriate type and use it safely.

Now! In terms of garbage collection, there is nothing special to know. Yes, passing the reference r into a ParameterizedThreadStart will create a copy of the reference (not a copy of the object), so will prevent garbage collection until it is no longer in scope. The same, however, is also true of the original new Thread(r.Start) approach. The only time it gets tricky is the "captured variable" example (() => r.Start()), although the problem you are seeing has nothing whatsoever to do with GC, and everything to do with the rules of captured variables.

like image 187
Marc Gravell Avatar answered Feb 23 '23 06:02

Marc Gravell


A lot of questions of which I don't understand all. What I can say is that

Thread t = new Thread(r.Start)

creates a delegate which will internally point to the instance that provides the method implementation to be called through the delegate. Hence, setting r to null will have no effect as there is a relationship of Thread -> ParameterizedDelegate -> your method -> RunMeBaby.

r = new RunMeBaby();
t = new Thread(() => r.Start());

creates a closure around r, which will still allow you to modify r. If you set it to null and access it a a later point, you get your NRE.

like image 41
flq Avatar answered Feb 23 '23 06:02

flq