QueueUserWorkItem does not work with delegate, but WaitCallBack works

In the next question, I found this neat trick for calling QueueUserWorkItem in a safe way, where you pass the delegate instead of WaitCallBack and the object. However, it does not work as one would expect.

What is the difference between QueueUserWorkItem () and BeginInvoke () to perform asynchronous activity without the necessary return types

Here is sample code and output that demonstrates the problem.

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

 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 a closure to call QueueUserWorkitem i = 10 to call always, but when using WaitCallBack you get the correct values ​​0-9.

So my questions are:

  • Why am I not passing the wrong value when using the close / delegation method?
  • How can i get 10? In the loop, it always had values ​​0-9 right?
+4
source share
2 answers

The answers to both questions are related to the closing area when creating an anonymous method.

When you do this:

 // Closure for anonymous function call begins here. for (int i = 0; i < 10; ++i) { // i is captured ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", i); }); } 

You capture i throughout the cycle. This means that you queue your ten threads very quickly, and by the time they start, closing i will be 10.

To get around this, you reduce the scope by introducing a variable inside the loop, for example:

 for (int i = 0; i < 10; ++i) { // Closure extends to here. var copy = i; // **copy** is captured ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", copy); }); } 

Here, the closure does not extend beyond the loop, but only to the value inside.

However, the second call to QueueUserWorkItem gives the desired result, because you created Tuple<T1, T2> while the delegate is in the queue, the value is fixed at this point.

Note that in C # 5.0, the behavior for foreach been changed because it happens so often (when the closure closes in a loop) and causes a lot of people headaches (but not for , as you use).

If you want to take advantage of this fact, you can call the Range method on the Enumerable class to use foreach :

 foreach (int i in Enumerable.Range(0, 10)) { // Closure for anonymous function call begins here. ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", i); }); } 
+6
source

This is because of how the variables are captured: the delegate will take the value i during the actual execution, and not during the declaration, so by then they are all 10. Try a copy of the local variable:

 for (int i = 0; i < 10; ++i) { int j = i; ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", j); }); 
+2
source

Source: https://habr.com/ru/post/1440822/


All Articles