我有一个例子,每次我使用迭代器时我都可以使用它,但是对于for循环它可以正常工作。所有代码都使用执行方法的本地变量。我很难过。有一个关于迭代器的事实我不知道,或者.Net中有一个诚实的善良错误。我打赌前者。请帮助。
此代码每次都可靠地工作。它一次一个地循环(假设10)所有元素并启动一个新线程,将整数作为方法中的参数传递给新线程。它启动10个线程,每个项目一个。 1,2,3,4,5,6,7,8,9,10 - 这总是有效的。
工作代码:
//lstDMSID is a populated List<int> with 10 elements.
for(int i=0; i<lstDMSID.Count; i++)
{
int dmsId = lstDMSID[i];
ThreadStart ts = delegate
{
// Perform some isolated work with the integer
DoThreadWork(dmsId);
};
Thread thr = new Thread(ts);
thr.Name = dmsId.ToString();
thr.Start();
}
这段代码实际上重复了元素。它一次迭代(比如说10)所有元素并开始一个新线程。它启动10个线程,但它不能可靠地获得所有10个整数。我看到它开始1,2,3,3,6,7,7,8,9,10。我正在失去数字。
BUSTED CODE:
//lstDMSID is a populated List<int> with 10 elements.
foreach(int dmsId in lstDMSID)
{
ThreadStart ts = delegate
{
// Perform some isolated work with the integer
DoThreadWork(dmsId);
};
Thread thr = new Thread(ts);
thr.Name = dmsId.ToString();
thr.Start();
}
答案 0 :(得分:12)
问题是基于为您的范围生成的闭包......
同样的问题会在你的for循环中发生,你是否会像这样重写它( BAD CODE!):
// ...Closure now happens at this scope...
for(int i=0;i<lstDMSID.Count;i++)
{
ThreadStart ts = delegate
{
DoThreadWork(lstDMSID[i]); // Eliminate the temporary, and it breaks!
};
Thread thr = new Thread(ts);
thr.Name = dmsId.ToString();
thr.Start();
}
问题是,当您关闭委托中的变量(在您的情况下为dmsId
)时,闭包发生在声明变量的范围内。当你使用for或foreach循环时,闭包发生在for / foreach语句的范围内,这个级别太高了。
在foreach循环中引入临时变量 将解决问题:
foreach(int dmsId in lstDMSID)
{
int temp = dmsId; // Add temporary
ThreadStart ts = delegate
{
DoThreadWork(temp); // close over temporary, and it's fixed
};
Thread thr = new Thread(ts);
thr.Name = dmsId.ToString();
thr.Start();
}
有关正在发生的事情的更详细讨论,我建议您阅读Eric Lippert's blog post: "Closing over the loop variable considered harmful"。
答案 1 :(得分:3)
这是因为你在闭包中使用的变量的范围。
Eric Lippert有一个nice blog post explaining this详细信息,我认为其他人(Jon Skeet?)也有关于此的博客。
答案 2 :(得分:3)
在foreach
中执行匿名方法时,编译器基本上生成一个指向dmsId的类。因此,当线程开始时,每个都指向相同的变量,因此根据线程的计划时间,您将看到数字被复制或跳过。
在for循环中,您正在复制整数,因此每个线程都有自己的值。
这个问题有一些很好的数据here。
答案 3 :(得分:2)
问题是闭包关闭了变量,而不是值。这意味着所有委托都获得对同一变量的引用,并且每次循环时变量的值都会改变
这应该解决它:
//lstDMSID is a populated List with 10 elements.
foreach(int dmsId in lstDMSID)
{
int tempId = dmsId;
ThreadStart ts = delegate
{
//this is method that goes off ad does some isolated work with the integer
DoThreadWork(tempId);
};
Thread thr = new Thread(ts);
thr.Name = tempId.ToString();
thr.Start();
}
答案 4 :(得分:1)
在第一种情况下,dmsId在for循环的范围内声明,每个委托捕获它自己的变量“实例”。
在第二个版本中,dmsId是为foreach循环的整个范围声明的。每个委托都捕获相同的变量 - 这意味着你从几个没有锁定的线程访问同一个变量 - 可能会发生坏事。