1

I can't make heads or tails of the following behaviour (please see comments in code)

for (int i = 0; i < 1000; i++)
{
    string debugString = String.Concat("Inside Action ", i);
    Action newAction = new Action(() => { System.Console.WriteLine(debugString); });
    Task.Run(newAction);        // outputs: 0, 1, 2, 3 (not always exactly in this order, but this is understandable because of async execution)

    Action newAction2 = new Action(() => { System.Console.WriteLine("Inside Action2 " + i); });
    Task.Run(newAction2);        // outputs: 1000, 1000, 1000, 1000, 1000 (...which is the value of i *after* the iteration, why?? "i" is a primitive int?!)
}
Console.ReadKey();

I don't understand the second behaviour. The variable i should be assigned to the Action as an Instance as the Action gets created. It looks like a pointer to i is created which has the value 1000 (last value of iteration) at the time the task is run...

Please explain. Thank you.

Florian
  • 100
  • 9

2 Answers2

2

You have a typical captured variable scenario:

for (int i = 0; i < 1000; i++) {
  ...
  Action newAction2 = new Action(() => { 
    System.Console.WriteLine("Inside Action2 " + i); });

  Task.Run(newAction2);

All threads use i variable from for loop. However, when threads have started, loop has completed, and thus i == 1000. That's why all the threads return 1000.

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
2

In the second example:

Action newAction2 = new Action(() => { System.Console.WriteLine("Inside Action2 " + i); });

the variable i is captured into the lambda. Not the value of i, but the variable i. That means that the value is whatever the loop happens to be at the moment that line of the task actually gets executed, not what it was when the task was created.

One lazy fix is to snapshot the value:

var tmp = i;
Action newAction2 = new Action(() => { System.Console.WriteLine("Inside Action2 " + tmp ); });

Importantly, the scope for "capture" purposes is defined by where the variable is defined. If we define tmp inside the loop, then that is the scope, hence: different tmp (semantically speaking) each loop iteration. However, in a for loop, the scope definition is technically outside the loop, hence why it is shared over all iterations.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • The "snapshot" is what I did (unknowingly) with the first example where I used i in the string-building. Your answer is reproducable und well explained. Thank you. (Seams I have to learn more about lambda expressions) – Florian Jun 07 '19 at 15:12
  • P.S: this answer was so quick, that I can only mark it as correct in another 5 minutes ;-) – Florian Jun 07 '19 at 15:13