代码之家  ›  专栏  ›  技术社区  ›  Jeremy Samuel

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

  •  7
  • Jeremy Samuel  · 技术社区  · 15 年前

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

    这段代码实际上重复了元素。它一次迭代(比如10)所有元素,并启动一个新线程。它启动10个线程,但不能可靠地获取所有10个整数。我看到它从1,2,3,3,6,7,7,8,9,10开始。我输了。

    破译代码:

    //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 回复  |  直到 15 年前
        1
  •  12
  •   Reed Copsey    15 年前

    问题是基于为您的作用域生成的闭包…

    同样的问题也会发生在for循环中,如果你这样重写它的话。( 坏代码! ):

    // ...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" .

        2
  •  3
  •   Lucero    15 年前

    这是因为您在闭包中使用的变量的范围。

    埃里克·利珀特 nice blog post explaining this 在细节上,我认为其他人(乔恩·斯基特?)也写过博客。

        3
  •  3
  •   dsolimano    15 年前

    在您的 foreach 编译器基本上正在生成一个指向dmsid的类。因此,随着线程的启动,每个线程都指向同一个变量,因此根据线程的调度时间,您将看到重复或跳过的数字。

    在for循环中,您要复制整数,以便每个线程都获得自己的值。

    关于这个问题有一些很好的数据 here .

        4
  •  2
  •   Gabe Timothy Khouri    15 年前

    问题是闭包关闭变量,而不是值。这意味着所有委托都将获得对同一变量的引用,并且变量的值每次都会通过循环进行更改。

    这应该可以修复它:

    //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();
    }
    
        5
  •  1
  •   leeeroy    15 年前

    在第一种情况下,DMSID在for循环的范围内声明,每个委托捕获它自己的该变量的“实例”。

    在第二个版本中,将为foreach循环的整个范围声明dmsid。每个委托捕获同一个变量——这意味着您正在从多个线程访问同一个变量,而没有锁定——可能会发生坏事情。