C#线程似乎启动了多次

本文关键字:启动 线程 | 更新日期: 2023-09-27 17:58:19

这个程序应该将.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();
    }

有人有类似的问题吗?

感谢

C#线程似乎启动了多次

您正在经历一个"访问修改的闭包"错误的痛苦。这是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();                
    }

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

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

变量CCD_ 2在CCD_ 3循环的每次迭代中被重写。这意味着在执行新线程时,new Thread中lambda捕获的对s的引用已经改变。

第一个线程将成功执行,但其余线程也指向相同的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的问题,并使运行时可以控制线程行为。