-3

Is this behavior defined in c#?

static void Main(string[] args)
{
    for (int i = 0; i < 10; i++)
    {
        Task.Run(() => Console.WriteLine("This is task " + i));
    }

    Console.ReadLine();
}

Output

This is task 10
This is task 10
This is task 10
This is task 10
This is task 10
This is task 10
This is task 10
This is task 10
This is task 10
This is task 10

I know how to fix it and i know why it happens but is it a defined behavior?

Gusdor
  • 14,001
  • 2
  • 52
  • 64
  • 1
    @TimSchmelter _I know how to fix it and i know why it happens_. – Gusdor Feb 24 '14 at 15:42
  • 2
    What do you mean by a "defined behavior?" – Robert Harvey Feb 24 '14 at 15:42
  • 2
    @gusdor: so have you read that answer and the links to eric lippert blogs it provides? If so, which part did you not understand? – Tim Schmelter Feb 24 '14 at 15:43
  • I'll include a link to the relevant Eric Lippert blog series [here](http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx) – Sven Grosen Feb 24 '14 at 15:43
  • @TimSchmelter i found the link. Mr Skeet's answer is useful but the question is not a duplicate. – Gusdor Feb 24 '14 at 15:44
  • Yes, it is defined behavior. In [Eric Lippert's Blog](http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx) he explains that they took a breaking change in C# 5 to fix it. – Robert Harvey Feb 24 '14 at 15:44
  • @RobertHarvey in c# 5.0 change is done only for `foreach` loop and here it is `for loop`. So no change in the behavior.:) – Sriram Sakthivel Feb 24 '14 at 15:45
  • @Gusdor It's generally expected that a lambda captures a variable, not the current value of the variable. For example, even without a loop, `int i = 1; Task.Run(() => Console.WriteLine("This is task " + i)); ++i;` will print "This is task 2" for all versions of C#, unless the background task reads `i` before `++i` increments it. –  Feb 24 '14 at 15:46
  • @RobertHarvey i suppose the important clause is `Closures close over variables, not over values.` – Gusdor Feb 24 '14 at 15:48

1 Answers1

5

No, it is not defined behaviour. It is a race condition, where each output may be any value from 0 to 10, in any order. It depends on the timing of the execution of the generated tasks with respect to the main program; this, in turn, largely depends on the multiprocessing capabilities of your machine. If a task happens to be picked up by a worker thread and executed before the loop was completed, then you would get whatever value happened to be in the loop counter at that point in time.

The reason that you often get just the final value of the loop counter is that the main loop is computationally trivial, and completes before any of the tasks get a chance to run. If you want to observe the non-determinism, add an artificial delay to the loop:

for (int i = 0; i < 10; i++)
{
    Task.Factory.StartNew(() => Console.WriteLine("This is task " + i));
    Thread.SpinWait(20000);
}

Sample output on an Intel Core i7:

This is task 2
This is task 3
This is task 3
This is task 3
This is task 4
This is task 5
This is task 7
This is task 8
This is task 9
This is task 8

If you use a sufficiently-large delay, the tasks would get the chance to run in the intuitively-expected order, outputting 0, 1, 2, ..., 9.

Douglas
  • 53,759
  • 13
  • 140
  • 188