此程序应将.txt文件重命名为.txtok。在我的测试目录中,我创建了~10个文本文件。
在运行时,抛出了FileNotFoundException。丢失的文件是一个已经在之前的线程中重命名的文件。
似乎多个线程已经在一个Loop-Iteration中启动了!?
static void Main(string[] args)
{
foreach (String s in Directory.EnumerateFiles(@"C:\Test", "*.txt", SearchOption.TopDirectoryOnly))
{
new Thread(() =>
{
File.Move(s, s + "ok");
}).Start();
}
Console.ReadKey();
}
有没有人有问题吗?
由于
答案 0 :(得分:12)
您正在经历“访问已修改的闭包”错误的痛苦。这是StackOverflow上报告的最常见问题之一。搜索“访问修改后的闭包”以获取更多详细信息,或阅读我关于该主题的文章:
http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/
您可以通过升级到C#5或执行以下操作来解决此问题:
foreach (String s in Directory.EnumerateFiles(@"C:\Test", "*.txt", SearchOption.TopDirectoryOnly))
{
string s1 = s;
new Thread(() =>
{
File.Move(s1, s1 + "ok");
}).Start();
}
那说,这段代码不是很好的代码;不要创建这么多的线程。线程是重量级的。善待创建一个像招聘新员工一样的线程;你不会雇用员工来重命名文件然后解雇他们;太贵。您雇用一名员工重命名所有文件。
答案 1 :(得分:1)
问题是由foreach
循环和lambda捕获之间的交互引起的。
在s
循环的每次迭代中都会覆盖变量foreach
。这意味着s
中lambda捕获的new Thread
引用在新线程执行时已更改。
第一个线程将成功执行,但其余的线程也指向s
的相同值并将失败。
解决方案是创建一个临时变量:
foreach (String s in Directory.EnumerateFiles(@"C:\Test", "*.txt", SearchOption.TopDirectoryOnly))
{
var temp = s;
new Thread(() => File.Move(temp, temp + "ok")).Start();
}
根据@Eric Lippert的建议,考虑使用PLINQ或TPL为您管理线程:
// assume `using System.Linq`
Directory.EnumerateFiles(@"C:\Test", "*.txt", SearchOption.TopDirectoryOnly)
.AsParallel()
.Select(f => File.move(f, f + "ok"))
.ToList();
这巧妙地避免了foreach
的问题,并提供了对线程行为的运行时控制。