0

Very simple scenario. I am running a tight loop, generating tasks with each iteration. (Don't ask why.)

static void Main(string[] args)
{
    for (int i = 0; i < 1000 * 1000; i++)
        Task.Run(() => WriteHelloAsync(i));

    Console.ReadLine();
}

static Task WriteHelloAsync(int x)
{
    Console.WriteLine($"Hello {x}!");

    return Task.CompletedTask;
}

I can see in the console that it writes the iteration number incrementally. However, when it reaches the target 1 million, it just keeps on going.

enter image description here

But why?

Gigi
  • 28,163
  • 29
  • 106
  • 188

1 Answers1

2

This is the classic loop-variable closure problem in C#.

Your loop does stop at a million.

After it stops at a million, you make some large number of calls to WriteHelloAsync(i).

You're passing i to all of them. That lambda you're passing to Task.Run() isn't creating a new copy of i that holds the value i had at the time of that particular call to Task.Run(). They all have the same i, whose value keeps changing until the loop ends.

On my machine here, the first number I see is well into six digits, meaning that's the value of i the first time a task actually gets around to calling WriteHelloAsync(). Very shortly thereafter, it starts doing the million-million-million thing you see.

Try this:

static void Main(string[] args)
{
    for (int i = 0; i < 1000 * 1000; i++)
    {
        //  The scope of j is limited to the loop block.
        //  For each new iteration, there is a new j. 
        var j = i;
        Task.Run(() => WriteHelloAsync(j));
    }

    Console.ReadLine();
}

@Gigi points out that this will also work with a foreach rather than for:

foreach (var i in Enumerable.Range(0, 1000 * 1000))
{
    Task.Run(() => WriteHelloAsync(i));
}