Reputation:
In this example, I'm attempting to pass by value, but the reference is passed instead.
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(() => new PhoneJobTest(i));
t.Start();
}
This can be remedied like so:
for (int i = 0; i < 10; i++)
{
int jobNum = i;
Thread t = new Thread(() => new PhoneJobTest(jobNum));
t.Start();
}
What's is going on here? Why does the original example pass the reference?
Upvotes: 12
Views: 4901
Reputation: 1635
When using an anonymous delegate or a lambda expression a closure is created so outside variables can be referenced. When the closure is created the stack (value) variables are promoted to the heap.
One way to avoid this is to start the thread with a ParameterizedThreadStart delegate. E.G.:
static void Main()
{
for (int i = 0; i < 10; i++)
{
bool flag = false;
var parameterizedThread = new Thread(ParameterizedDisplayIt);
parameterizedThread.Start(flag);
flag = true;
}
Console.ReadKey();
}
private static void ParameterizedDisplayIt(object flag)
{
Console.WriteLine("Param:{0}", flag);
}
Coincidentally I ran into this concept just yesterday: Link
Upvotes: 0
Reputation: 564333
This is easier to understand if you look at what happens, in terms of scope:
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(() => new PhoneJobTest(i);
t.Start();
}
Basically translates to something very close to this:
int i = 0;
while (i < 10)
{
Thread t = new Thread(() => new PhoneJobTest(i);
t.Start();
i++;
}
When you use a lambda expression, and it uses a variable declared outside of the lambda (in your case, i
), the compiler creates something called a closure - a temporary class that "wraps" the i variable up and provides it to the delegate generated by the lambda.
The closure is constructed at the same level as the variable (i), so in your case:
int i = 0;
ClosureClass = new ClosureClass(ref i); // Defined here! (of course, not called this)
while (i < 10)
{
Thread t = new Thread(() => new PhoneJobTest(i);
t.Start();
i++;
}
Because of this, each Thread gets the same closure defined.
When you rework your loop to use a temporary, the closure is generated at that level instead:
for (int i = 0; i < 10; i++)
{
int jobNum = i;
ClosureClass = new ClosureClass(ref jobNum); // Defined here!
Thread t = new Thread(() => new PhoneJobTest(jobNum);
t.Start();
}
Now, each Thread gets its own instance, and everything works properly.
Upvotes: 18
Reputation: 180884
You definitely want to read Eric Lippert's "Closing over the loop variable considered harmful":
In Short: The behavior you see is exactly how C# works.
Upvotes: 2
Reputation: 27419
It happens because of the way C# passes parameters to a lambda. It wraps the variable access in a class which is created during compilation, and exposes it as a field to the lambda body.
Upvotes: 1
Reputation: 1673
Short answer: closures. Long answer given here (among other places): Differing behavior when starting a thread: ParameterizedThreadStart vs. Anonymous Delegate. Why does it matter?
Upvotes: 3
Reputation: 71937
Well, that's just how C# works. The lambda expression in your statement constructs a lexical closure, which stores a single reference to i
that persists even after the loop has concluded.
To remedy it, you can do just the thing that you did.
Feel free to read more on this particular issue all around the Web; my choice would be Eric Lippert's discussion here.
Upvotes: 19