C# 为什么迭代器(.Net)在这段代码中不可靠

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

我有一个例子,每次使用迭代器时,我都会中断,但它在for循环中运行良好。所有代码都使用执行方法的局部变量。我被难住了。我不知道迭代器有一个事实,或者.Net中有一个诚实的bug。我赌前者。请帮忙

这段代码每次都能可靠地工作。它一次循环(比方说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();
 }
//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();
}