我正在尝试在我的应用程序中实现任务。
以下是示例代码:
有一个简单的接口I,从它派生出3个类(A,B,C) 我创建一个Is列表,用A,B,C实例弹出它,然后为彼此创建一个任务来调用方法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 : 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();
}
}
我期待看到
A
B
C
在输出中,而不是我得到
C
C
C
我在调试器中找到了问题:所有任务都有相同的委托,但我不知道为什么以及如何解决这个问题。
答案 0 :(得分:7)
这是一个非常常见的问题。它涉及“捕获的变量”如何工作;简短版本,你需要这个:
foreach (var i in l)
{
var copy = i;
Task task = new Task( () => copy.do1());
TaskPool.Add(task);
}
这里的问题是i
(来自foreach
)技术上声明在之外的循环范围,因此被捕获在外部范围内;每次捕获相同的变量(C#捕获变量,而不是*值)。在中添加copy
环路范围会改变这一点;由于范围,copy
每次迭代单独捕获。
答案 1 :(得分:5)
问题是在分配代理时捕获循环变量i
。如果在分配之前创建临时副本,则可以避免捕获变量,从而获得所需的值。
foreach (var i in l)
{
var local = i;
Task task = new Task( () => local.do1());
TaskPool.Add(task);
}
答案 2 :(得分:3)
这是linq表达式的预期行为。它有点被称为变量捕获。有关此主题的一些详细信息,请参阅this link。
在您的情况下,只需用方法组替换linq表达式。我的意思是: 此...
Task task = new Task(i.do1);
而不是......
Task task = new Task(() => i.do1());
编辑:还有一件非常重要的事情。您已通过添加A,B,C(按特定顺序)将项目添加到列表中。这并不能保证A任务在B任务之前运行,依此类推。你可以得到任何东西作为输出,ABC,ACB等。