In the question below, I found this neat trick for calling QueueUserWorkItem in a type safe way, where you pass a delegate instead of WaitCallBack and an object. However, it doesn’t work the way one would expect.
Here’s some sample code and output that demonstrates the issue.
for (int i = 0; i < 10; ++i)
{
// doesn't work - somehow DoWork is invoked with i=10 each time!!!
ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", i); });
// not type safe, but it works
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), Tuple.Create(" WCB", i));
}
void DoWork(string s, int i)
{
Console.WriteLine("{0} - i:{1}", s, i);
}
void DoWork(object state)
{
var t = (Tuple<string, int>)state;
DoWork(t.Item1, t.Item2);
}
and here is the output:
closure - i:10
WCB - i:0
closure - i:10
WCB - i:2
WCB - i:3
closure - i:10
WCB - i:4
closure - i:10
WCB - i:5
closure - i:10
WCB - i:6
closure - i:10
WCB - i:7
closure - i:10
WCB - i:8
closure - i:10
WCB - i:9
WCB - i:1
closure - i:10
Note that when using the closure to call QueueUserWorkitem, i=10 for ever call, but when using the WaitCallBack you get the correct values, 0-9.
So my questions are:
- Why isn’t the correct value of i passed when using the closure/delegate way of doing it?
- How on earth does i ever get to be 10? In the loop, it only ever had values 0-9 right?
The answers to both of your question are related to the scope of the closure when you create the anonymous method.
When you do this:
You’re capturing
iacross the entire loop. That means that you queue up your ten threads very quickly, and by the time they start, the closure has capturedito be 10.To get around this, you reduce the scope of the closure, by introducing a variable inside the loop, like so:
Here, the closure doesn’t extend beyond the loop, but just to the value inside.
That said, the second call to the
QueueUserWorkItemproduces the desired result because you’ve created theTuple<T1, T2>at the time that the delegate is being queued up, the value is fixed at that point.Note that in C# 5.0, the behavior for
foreachwas changed because it happens so often (where the closure closes over the loop) and causes a number of people a lot of headaches (but notforlike you are using).If you want to take advantage of that fact, you can call the
Rangemethod on theEnumerableclass to useforeach: