2008-10-30 61 views
12

我有以下代码的简单应用:C#2.0线程问(匿名方法)

FileInfo[] files = (new DirectoryInfo(initialDirectory)).GetFiles(); 
    List<Thread> threads = new List<Thread>(files.Length); 

    foreach (FileInfo f in files) 
    { 
     Thread t = new Thread(delegate() 
     { 
      Console.WriteLine(f.FullName); 
     }); 
     threads.Add(t); 
    } 

    foreach (Thread t in threads) 
     t.Start(); 

允许在 'I = initialDirectory' 目录我有3个文件说。然后,此应用程序应创建3个线程,每个线程打印一个文件名;但是,每个线程将打印出'文件'数组中最后一个文件的名称。

这是为什么?为什么当前文件'f'变量没有正确设置匿名方法?

回答

11

匿名方法将参考保留在封闭块中的变量 - 而不是变量的实际值。

当方法被实际执行时(当你启动线程时)f已被分配指向集合中的最后一个值,因此所有3个线程都会打印最后一个值。

+1

请注意未来的读者:在C#5.0中这种行为[实际上会改变](http://stackoverflow.com/a/8899347/137188)。每次迭代都会创建一个新的单独的循环变量。随着这一变化,这个问题中的代码将按照原先的预期行事。 – tcovo 2012-01-24 17:43:45

0

这是因为f.FullName是一个变量的引用,而不是一个值(这是你如何使用它)。当你真正开始线程时,f.FullName一直增加到数组的末尾。

无论如何,为什么在这里遍历事物两次?

foreach (FileInfo f in files) 
{ 
    Thread t = new Thread(delegate() 
    { 
     Console.WriteLine(f.FullName); 
    }); 
    threads.Add(t); 
    t.Start(); 
} 

然而,这仍然是错误的,甚至可能更糟,因为你现在有一个竞争条件,看看哪个线程去更快:写控制台项目或迭代到下一个FileInfo的。

+0

在此代码中,线程在主线程完成修改之前访问f变量。 – 2008-10-30 15:38:52

6

以下是有关在C#匿名方法和将由编译器生成的代码一些很好的文章:

http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/03/687529.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx

我认为,如果你这样做:

 
    foreach (FileInfo f in files) 
    { 
     FileInfo f2 = f; //variable declared inside the loop 
     Thread t = new Thread(delegate() 
     { 
      Console.WriteLine(f2.FullName); 
     }); 
     threads.Add(t); 
    } 

它会以你想要的方式工作。

+0

是的,它会。停止修改这个答案。 – Jimmy 2008-10-30 14:44:36

+0

是的,果然有效!谢谢! 我原本以为foreach循环是自动执行的(每次迭代都会产生新的'f'变量),但我想这对于它的工作没有任何意义, – John 2008-10-30 15:25:57

0

这是因为迭代器(foreach)的底层代码已经在线程启动前迭代了List中的所有值......所以当它们启动时,迭代器指向的值是最后一个一个列表...

启动迭代,而不是内部的线程....

foreach (FileInfo f in files) 
{ 
    string filName = f.FullName; 
    Thread t = new Thread(delegate() 
       { Console.WriteLine(filName); }); 
    t.Start(); 
} 

我不认为一场比赛是可能的,因为这里有来自所有线程访问没有共享内存。

+0

有一个竞争条件,f是共享的。您可以通过使每个线程休眠几毫秒来进行测试:线程t =新线程(委托() { Thread.Sleep(300); Console.WriteLine(f.FullName); }); – 2008-10-30 21:05:27

0

以下内容也适用。

Thread t = new Thread(delegate() 
    { 
     string name = f.Name; 
     Console.WriteLine(name); 
    });