C# 为什么迭代器(.Net)在这段代码中不可靠
我有一个例子,每次使用迭代器时,我都会中断,但它在for循环中运行良好。所有代码都使用执行方法的局部变量。我被难住了。我不知道迭代器有一个事实,或者.Net中有一个诚实的bug。我赌前者。请帮忙 这段代码每次都能可靠地工作。它一次循环(比方说10个)所有元素,并启动一个新线程,将整数作为方法中的参数传递给新线程。它启动10个线程,每个项目一个。 1,2,3,4,5,6,7,8,9,10-这始终有效 工作代码:C# 为什么迭代器(.Net)在这段代码中不可靠,c#,.net,C#,.net,我有一个例子,每次使用迭代器时,我都会中断,但它在for循环中运行良好。所有代码都使用执行方法的局部变量。我被难住了。我不知道迭代器有一个事实,或者.Net中有一个诚实的bug。我赌前者。请帮忙 这段代码每次都能可靠地工作。它一次循环(比方说10个)所有元素,并启动一个新线程,将整数作为方法中的参数传递给新线程。它启动10个线程,每个项目一个。 1,2,3,4,5,6,7,8,9,10-这始终有效 工作代码: //lstDMSID is a populated List<int> w
//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();
}
//lstDMSID是一个包含10个元素的填充列表。
for(int i=0;i在第一种情况下,dmsId在for循环的范围内声明,每个委托捕获该变量自己的“实例”
在第二个版本中,为foreach循环的整个范围声明dmsId。每个委托捕获相同的变量-这意味着您正在从多个线程访问相同的变量,而没有锁定-可能会发生错误。这是因为您在闭包中使用的变量的范围
Eric Lippert有一篇详细的文章,我想其他人(Jon Skeet?)也在博客上写过这篇文章。问题是基于为您的范围生成的闭包
同样的问题也会发生在for循环中,如果您像这样重写它(糟糕的代码!):
要更详细地讨论正在发生的事情,我建议阅读。在您的foreach
中执行匿名方法时,编译器基本上是生成一个指向dmsId的类。因此,当线程开始时,每个线程都指向同一个变量,因此根据线程的调度时间,您将看到数字重复或跳过了
在for循环中,创建整数的副本,以便每个线程获得自己的值
关于这个问题有一些很好的数据。问题在于闭包关闭变量,而不是值。这意味着所有代理都获得了对同一变量的引用,并且变量的值在循环中每次都会更改
这应该可以解决这个问题:
//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();
}
需要注意的是,重复的整数并不总是相同的。它似乎是随机的。这仍然会中断-这基本上与问题相同。引入的变量必须在foreach循环内确定范围。然后将正确生成闭包。不,我在写完答案之前意外地点击了submit。它现在的工作方式如图所示@Reed Copsey-greate解释和伟大的参考文章。基于答案的数量,我可能应该知道这一点。我现在知道了!@Jride:这是许多开发人员错过的事情,因为在实现线程(大多数时候)之前,这似乎不是一个问题。我总是在并行性讨论中提到这一点,正是出于这个原因。
// ...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();
}
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();
}
//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();
}