OopsUser
OopsUser

Reputation: 4774

How closure in c# works when using lambda expressions?

In to following tutorial : http://www.albahari.com/threading/

They say that the following code :

for (int i = 0; i < 10; i++)
  new Thread (() => Console.Write (i)).Start();

is non deterministic and can produce the following answer :

0223557799

I thought that when one uses lambda expressions the compiler creates some kind of anonymous class that captures the variables that are in use by creating members like them in the capturing class. But i is value type, so i thought that he should be copied by value.

where is my mistake ?

It will be very helpful if the answer will explain how does closure work, how do it hold a "pointer" to a specific int , what code does generated in this specific case ?

Upvotes: 12

Views: 1836

Answers (3)

Servy
Servy

Reputation: 203850

The key point here is that closures close over variables, not over values. As such, the value of a given variable at the time you close over it is irrelevant. What matters is the value of that variable at the time the anonymous method is invoked.

How this happens is easy enough to see when you see what the compiler transforms the closure into. It'll create something morally similar to this:

public class ClosureClass1
{
    public int i;

    public void AnonyousMethod1()
    {
        Console.WriteLine(i);
    }
}

static void Main(string[] args)
{
    ClosureClass1 closure1 = new ClosureClass1();
    for (closure1.i = 0; closure1.i < 10; closure1.i++)
        new Thread(closure1.AnonyousMethod1).Start();
}

So here we can see a bit more clearly what's going on. There is one copy of the variable, and that variable has now been promoted to a field of a new class, instead of being a local variable. Anywhere that would have modified the local variable now modifies the field of this instance. We can now see why your code prints what it does. After starting the new thread, but before it can actually execute, the for loop in the main thread is going back and incrementing the variable in the closure. The variable that hasn't yet been read by the closure.

To produce the desired result what you need to do is make sure that, instead of having every iteration of the loop closing over a single variable, they need to each have a variable that they close over:

for (int i = 0; i < 10; i++)
{
    int copy = i;
    new Thread(() => Console.WriteLine(copy));
}

Now the copy variable is never changed after it is closed over, and our program will print out 0-9 (although in an arbitrary order, because threads can be scheduled however the OS wants).

Upvotes: 11

Orel Eraki
Orel Eraki

Reputation: 12196

As Albahari states, Although the passing arguments are value types, each thread captures the memory location thus resulting in unexpected results.

This is happening because before the Thread had any time to start, the loop already changed whatever value that inside i.

To avoid that, you should use a temp variable as Albahari stated, or only use it when you know the variable is not going to change.

Upvotes: 2

LB2
LB2

Reputation: 4860

i in Console.Write(i) is evaluated right when that statement is about to be executed. That statement will be executed once thread has been fully created and started running and got to that code. By that time loop has moved forward a few times and thus i can be any value by then. Closures, unlike regular functions, have visibility into local variables of a function in which it is defined (what makes them useful, and way to shoot oneself in a foot).

Upvotes: 1

Related Questions