This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Captured Closure (Loop Variable) in C# 5.0
(1 answer)
C# Closures, why is the loopvariable captured by reference?
(6 answers)
How closure in c# works when using lambda expressions?
(3 answers)
Closed 3 years ago.
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.
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.
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.
Related
This question already has answers here:
Closures in C# - initating a thread in a function call with a value type
(3 answers)
Closed 3 years ago.
I have a problem with my multi threading app.
I have an array with 4 Threads in it.
Here is how I initialize them:
for (int i = 0; i < threads.Length; i++)
{
threads[i] = new Thread(delegate () { ThreadMethod(i); });
}
But after my first test(s) I showed that all four threads run with 3 as passed value.
I chagend it to
for (int i = 0; i < threads.Length; i++)
{
int id = i;
threads[i] = new Thread(delegate () { ThreadMethod(id); });
}
and it worked but for me it looks like there must be a better way to pass my i to my thread.
Am I right with my guess and if I am, how should I pass i?
Thank you!
The closure uses local variables -- in a for loop the variables declared for the loop are not local they have to be inside the block ({}) for that. That is why the 2nd example works.
The delegate keyword is what creates the closure and it looks for local variables to include in the closure. When it finds id it includes it. When it doesn't it references the global (outside the closure) value of i and at the time of running i has a value of 3.
In the preparation for a C# exam at university I found the following multiple choice question:
Client applications call your library by passing a set of operations
to perform. Your library must ensure that system resources are most
effectively used. Jobs may be scheduled in any order, but your
librarymust log the position of each operation. You have declared this
code:
public IEnumerable<Task> Execute(Action[] jobs)
{
var tasks = new Task[jobs.Length];
for (var i = 0; i < jobs.Length; i++)
{
/* COMPLETION NEEDED */
}
return tasks;
}
public void RunJob(Action job, int index)
{
// implementation omitted
}
Complete the method by inserting code in the for loop. Choose the
correct answer.
1.)
tasks[i] = new Task((idx) => RunJob(jobs[(int)idx], (int)idx), i);
tasks[i].Start();
2.)
tasks[i] = new Task(() => RunJob(jobs[i], i));
tasks[i].Start();
3.)
tasks[i] = Task.Run(() => RunJob(jobs[i], i));
I have opted for answer 3 since Task.Run() queues the specified work on the thread pool and returns a Task object that represents the work.
But the correct answer was 1, using the Task(Action, Object) constructor. The explanation says the following:
In answer 1, the second argument to the constructor is passed as the
only argument to the Action delegate. The current value of the
i variable is captured when the value is boxed and passed to the Task
constructor.
Answer 2 and 3 use a lambda expression that captures the i variable
from the enclosing method. The lambda expression will probably return
the final value of i, in this case 10, before the operating system
preempts the current thread and begins every task delegate created by
the loop. The exact value cannot be determined because the OS
schedules thread execution based on many factors external to your
program.
While I perfectly understand the explanation of answer 1, I don't get the point in the explanations for answer 2 and 3. Why would the lambda expression return the final value?
In options 2 and 3 lambda captures original i variable used in for loop. It's not guaranteed when tasks will be run on thread pool. So possible behavior: for loop is finished, i=10 and then tasks are started to execute. So all of them will use i=10.
Similar behavior you can see here:
void Do()
{
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
//actions executed after loop is finished
foreach(var a in actions)
{
a();
}
}
Output is:
3
3
3
You can fix it like this:
for (int i = 0; i < 3; i++)
{
var local = i;
actions.Add(() => Console.WriteLine(local));
}
This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Closed last month.
I have this code and do not understand why the out put is 22! I am afraid it should be 01!
can anyone explain what happens? if the list store a method with a parameter, so the parameters should be 0 and 1 respectively!
List<Action> list = new List<Action>();
for (int i = 0; i < 2; i++)
{
list.Add(() => Console.Write(i));
}
foreach (var it in list)
{
it();
}
It is Closure (1, 2).
In your case Console.Write(i) will use value of i in the moment of action call. You firstly increment i in for loop then in second loop you call every action in the list. In the moment of call of every action i has value 2 - so, you get 22 as output.
To get expected result you should create local copy of i and use it:
for (int i = 0; i < 2; i++)
{
var temp = i;
list.Add(() => Console.Write(temp));
}
Addition to Roma Doskoch's anwser, another approach is to avoid for.
var list = Enumerable
.Range(0, 2)
.Select<int, Action>(i => () => Console.Write(i));
Closures capture variables, not values.
In your code, the closure captures the variable i, not whatever value happens to be stored in i on each iteration. When you invoke the action, the variable i has a value of 2 (because the loop has finished) and therefore 2 will be printed out twice.
In order to avoid this, as other answers already point out, you need to create a new variable every time around as a workaround to not being able to capture values; if you declare a new variable on every iteration then the result of capturing the variable is equivalent to capturing the value because you won't be changing it on the next iteration.
This question already has answers here:
For-Loop and LINQ's deferred execution don't play well together
(2 answers)
Closed 7 years ago.
I'm trying to do parallel programming using Task in .Net 4.0 c#.
output of my program is little confusing.
class Program
{
static void Main(string[] args)
{
List<Task> lstTasks = new List<Task>();
for (int i = 0; i < 5; i++)
{
Task tsk = Task.Factory.StartNew(() => DoSomething(i.ToString()));
lstTasks.Add(tsk);
}
Task.WaitAll(lstTasks.ToArray());
Console.WriteLine("Done");
Console.ReadLine();
}
static void DoSomething(string tasKname)
{
Console.WriteLine(tasKname);
System.Threading.Thread.Sleep(10000);
}
}
Output is
5
5
5
5
5
Done.
I'm expecting.
0
1
2
3
4
Done.
where I'm going wrong?
You created a closure when you defined the function () => DoSomething(i.ToString()).
A closure is an anonymous function/lamdba that references some variables defined in the method where the closure was created. In your case, that's variable i.
When this function is executed, it will use the current value of i, not the value that i had when you created it.
You have to be aware that calling Task.Factory.StartNew will not start executing the task immediately. In your case, the tasks started executing after the for loop, so the value of i is 5.
To get the results you expect, use a separate variable in the loop to store the current value of i.
for (int i = 0; i < 5; i++)
{
int k = i;
Task tsk = Task.Factory.StartNew(() => DoSomething(k.ToString()));
lstTasks.Add(tsk);
}
You shouldn't expect the results in any particular order though.
You are accessing a variable that is changing within your loop. Essentially, your foreach runs so quickly, by the time DoSomething runs, i is 5. try this:
for (int i = 0; i < 5; i++)
{
Task tsk = Task.Factory.StartNew(() => DoSomething(i.ToString()));
lstTasks.Add(tsk);
Thread.Sleep(50);
}
and you should see your expected output in the console.
you say I'm expecting 0 1 2 3 4. But you shouldn't. The most important aspect of Tasks is you don't know when they'll complete. For example, when I alter your code to use a Parallel.Foreach():
Parallel.ForEach(Enumerable.Range(0, 5), i =>
{
Task tsk = Task.Factory.StartNew(() => DoSomething(i.ToString()));
lstTasks.Add(tsk);
});
I get the expected numbers, 0 through 4, but in a random order each time i run the code, because we are using Tasks that are all running independently of eachother.
This question already has answers here:
Using the iterator variable of foreach loop in a lambda expression - why fails?
(3 answers)
Closed 8 years ago.
I got this piece of code,
delegate void Printer();
static void Main(string[] args)
{
List<Printer> printers = new List<Printer>();
for (int i = 0; i < 10; i++)
{
printers.Add(delegate { Console.WriteLine(i); });
}
foreach (Printer printer in printers)
{
printer();
}
Console.ReadLine();
}
Here the output is '10' for ten times.
The scope of i is with in the for loop. But while we retrieve in out side that we are still getting value from i.
How is it possible?
You have modified closure. Try this:
for (int i = 0; i < 10; i++)
{
int ii = i;
printers.Add(delegate { Console.WriteLine(ii); });
}
When you use in your anonymous method access the the variable in you local scope it creates closure.
The code in the delegate is not run until it is called, which happens in the second loop. It then refers to the i which was defined within the scope of the first loop, but with it's current value - and since the first loop has been completed already, i will be 10 each time.
I believe each of the delegates you create are given the same scope as the first loop, if that makes sense. This means that each i has it's delegate as it's scope, and since each delegate is defined within the scope of the first loop, each i will also have the loop as it's scope, even if the delegate logic is called outside that scope, as in your example.
Since i is valid throughout / across several iterations of the loop, it gets updated, and is always 10 by the time the delegates get called.
This explains why the following works as a fix:
for(int i = 0; i < 10; i++)
{
var localVar = i; // Only valid within a single iteration of the loop!
printers.Add(delegate { Console.WriteLine(localVar); });
}
Let's unroll the loop:
int i=0;
printers.Add(delegate { Console.WriteLine(i); })
i=1;
printers.Add(delegate { Console.WriteLine(i); })
...
i=10;
printers.Add(delegate { Console.WriteLine(i); })
As you can see the i variable is captured within the delegate, and the delegate itself is not run until the loop ends, and the variable has achieved the last value (10).
A simple workaround is to assign the loop variable to a local helper variable
for (int i = 0; i < 10; i++)
{
var index = i;
printers.Add(delegate { Console.WriteLine(index); });
}
As for the scope issue, any captured variables have their scope (and lifetime) extended. A variable used within a lambda/delegate will not be garbage collected until the delegate itself goes out of scope - which can be a problem for large objects. Specifically, section, 7.15.5.1 of the C# 5 Specification states:
When an outer variable is referenced by an anonymous function, the
outer variable is said to have been captured by the anonymous
function. Ordinarily, the lifetime of a local variable is limited to
execution of the block or statement with which it is associated
(ยง5.1.7). However, the lifetime of a captured outer variable is
extended at least until the delegate or expression tree created from
the anonymous function becomes eligible for garbage collection.
Each delegate is only invoked in the foreach, after the for loop. By this time, the variable i captured by the closure is already at its final value, viz 10. You can solve like so:
for (int i = 0; i < 10; i++)
{
var cache = i;
printers.Add(delegate { Console.WriteLine(cache); });
}