views:

169

answers:

1

I just started playing around with threading today and I ran into something that I dont understand.

public void Main()
{ 
    int maxValue = 5;
    for (int ID = 0; ID < maxValue; ID++)
    {
        temp(ID);
    }
}

public void temp(int i)
{
    MessageBox.Show(i.ToString());
}

As basic as it gets which works fine, but when I try to create a new thread for each, it only passes the maxValue. Please disregard how bad this is to do, I only wrote it this way as a simplistic example.

public void Main()
{ 
    int maxValue = 5;
    for (int ID = 0; ID < maxValue; ID++)
    {
        threads.Add(new Thread(() => temp(myString, rowID)));
        threads[rowID].Start();
    }
}

public void temp(string myString, int i)
{
    string _myString = myString;

    MessageBox.Show(i.ToString());
}

Given this, I have two questions: 1) Why doesnt a the method get called on a new thread passing the ID? 2) How should this correctly be coded?

+6  A: 

The problem is that you've only got one ID variable, and it's being captured. The variable is only being read when the code in the new thread is actually executed, which will often be after your main thread has finished its loop, leaving ID at maxValue. Take a copy on each loop iteration, so that you capture a different variable each time:

for (int ID = 0; ID < maxValue; ID++)
{
    int copy = ID;
    threads.Add(new Thread(() => temp(myString, copy)));
    threads[rowID].Start();
}

This is a common mistake with closures. Read my article comparing C# and Java closures for more information. The same thing happens with foreach, by the way - and that's even more confusing, as it reads like you've got a new variable each time:

foreach (string url in urls)
{
    // Aargh, bug! Don't do this!
    new Thread(() => Fetch(url)).Start();
}

Again, you only end up with one variable. You need each delegate to capture a separate variable, so again you use a copy:

foreach (string url in urls)
{
    string urlCopy = url;
    new Thread(() => Fetch(urlCopy)).Start();
}
Jon Skeet
You've made the SAME mistake as DataPimp made. You don't need to have another variable, just rename rowID to ID and it'll work ok.
mnn
Thank you very much, Im not sure I understand 100% on why the first code I posted gets executed but the second doesnt (I guess it kinda makes sense) but Im definitely going to read that article of yours and hopefully that will clarify things.
Leroy Jenkins
@mnn: No it won't. You'll end up with the captured variable problem. It may not *always* show, but you'll definitely have a bug. Using a captured loop variable when starting threads is horribly bug-prone. I assumed the use of `rowID` was just a typo, given that the use of an undeclared variable is a *compile time* bug rather than an *execution time* bug.
Jon Skeet