我有一个简单的应用程序,代码如下:
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个线程,每个线程打印一个文件名;但是,每个线程都会打印出'files'数组中最后一个文件的名称。
这是为什么?为什么当前文件'f'变量没有在匿名方法中正确设置?
答案 0 :(得分:11)
匿名方法将引用保存到封闭块中的变量 - 而不是变量的实际值。
当方法实际执行时(启动线程时)f
已被指定为指向集合中的最后一个值,因此所有3个线程都打印该最后一个值。
答案 1 :(得分: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); }
它会以你想要的方式工作。
答案 2 :(得分: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。
答案 3 :(得分:0)
这是因为迭代器(foreach)的底层代码已经在线程开始之前“迭代”了List中的所有值...所以当它们启动时,迭代器“指向”的值是最后一个在列表中...
在迭代中启动线程而不是....
foreach (FileInfo f in files)
{
string filName = f.FullName;
Thread t = new Thread(delegate()
{ Console.WriteLine(filName); });
t.Start();
}
由于没有可从所有线程访问的共享内存,我不相信这里可以进行竞争。
答案 4 :(得分:0)
以下内容也可以。
Thread t = new Thread(delegate()
{
string name = f.Name;
Console.WriteLine(name);
});