How does linq lambdas work inside a loop?
I'm trying to implement Tasks in my Application.
Here's the sample code:
There's one simple Interface I, 3 classes are derived form it (A,B,C) I create a list of Is, poplualte it with A, B, C instances, and then create a task for each other to call method do1();
interface I
{
void do1();
}
class A : I
{
public void do1()
{
Console.WriteLine("A");
}
}
class B : I
{
public void do1()
{
Console.WriteLine("B");
}
}
class开发者_如何学C C : I
{
public void do1()
{
Console.WriteLine("C");
}
}
class Program
{
public static void Main(string[] args)
{
List<I> l = new List<I>();
l.Add(new A());
l.Add(new B());
l.Add(new C());
var TaskPool = new List<Task>();
foreach (var i in l)
{
Task task = new Task( () => i.do1()
);
TaskPool.Add(task);
}
foreach (var c in TaskPool)
{
c.Start();
}
Thread.Sleep(3000);
Console.Read();
}
}
I'm expecting to see
A
B
C
In the output, but instead of it I get
C
C
C
I kinda found the problem in debugger: All tasks have the same delegate, but I do not know why and how to workaround this.
This is a very common question. It relates to how "captured variables" work; short version, you need this:
foreach (var i in l)
{
var copy = i;
Task task = new Task( () => copy.do1());
TaskPool.Add(task);
}
The problem here is that i
(from the foreach
) is technically declared outside the scope of the loop, and thus is captured at that outer scope; you capture the same variable each time (C# captures variables, not *values). Adding copy
inside the loop-scope changes this; because of the scope, copy
is captured separately per iteration.
The problem is that you capture the loop variable i
when assigning the delegates. If you create a temporary copy before the assignment you can avoid capturing the variable and thus get the values you need.
foreach (var i in l)
{
var local = i;
Task task = new Task( () => local.do1());
TaskPool.Add(task);
}
This is the intended behavior for linq expressions. It is somewhat called variable capturing. Refer to this link for some good detailed information on this topic.
In your case, just replace the linq expression with a method group. I mean: this...
Task task = new Task(i.do1);
instead of this...
Task task = new Task(() => i.do1());
EDIT: And one more VERY important thing. You have added the items to the list by adding A, B, C (in that particular order). That does not guarantee that A task will run before B task and so on. You can get anything as the output, ABC, ACB and so on.
精彩评论