为什么迭代器(.Net)在此代码中不可靠

时间:2010-02-12 18:01:15

标签: c# .net

我有一个例子,每次我使用迭代器时我都可以使用它,但是对于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();
}

5 个答案:

答案 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循环的整个范围声明的。每个委托都捕获相同的变量 - 这意味着你从几个没有锁定的线程访问同一个变量 - 可能会发生坏事。