1

此程序应将 .txt 文件重命名为 .txtok。在我的测试目录中,我创建了 ~10 个文本文件。

在运行时,抛出了 FileNotFoundException。丢失的文件是已在前一个线程中重命名的文件。

似乎在一个循环迭代中启动了多个线程!?

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();
    }

有人有类似的问题吗?

谢谢

4

2 回答 2

12

您正在经历“访问修改后的闭包”错误的痛苦。这是 StackOverflow 上报告的最常见问题之一。搜索“访问修改后的闭包”以获取更多详细信息,或阅读我关于该主题的文章:

http://ericlippert.com/2009/11/12/closure-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();                
    }

也就是说,这段代码不是好代码;不要像那样创建这么多线程。线程是重量级的。像对待雇用新员工一样对待创建线程;您不会雇用员工重命名文件然后解雇他们;太贵。您将雇用一名员工来重命名所有文件。

于 2013-02-08T23:12:15.453 回答
1

该问题是由foreach循环和 lambda 捕获之间的交互引起的。

该变量在循环s的每次迭代中都会被覆盖foreach。这意味着在执行新线程时s,lambda 捕获的对 in的引用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并让运行时控制线程行为。

于 2013-02-08T23:14:21.320 回答