【问题标题】:C# 2.0 Threading Question (anonymous methods)C# 2.0 线程问题(匿名方法)
【发布时间】:2008-10-30 13:56:21
【问题描述】:

我有一个简单的应用程序,代码如下:

   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' 变量没有在匿名方法中正确设置?

【问题讨论】:

    标签: c# multithreading .net-2.0 anonymous-methods


    【解决方案1】:

    匿名方法保留对封闭块中变量的引用——而不是变量的实际值。

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

    【讨论】:

    • 未来读者的注意事项:这种行为 will actually change 在 C# 5.0 中。每次迭代都会创建一个新的、单独的循环变量。通过这种更改,此问题中的代码将按照提问者最初预期的方式运行。
    【解决方案2】:

    这里有一些关于 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) { 文件信息 f2 = f; //在循环中声明的变量 线程 t = 新线程(委托() { Console.WriteLine(f2.FullName); }); 线程。添加(t); }

    它会按照你想要的方式工作。

    【讨论】:

    • 是的,果然行得通!谢谢!我最初认为 foreach 循环会自动执行此操作(每次迭代新的 'f' 变量),但我想它这样工作并没有任何意义
    【解决方案3】:

    这是因为f.FullName 是对变量的引用,而不是值(这是您尝试使用它的方式)。当您实际启动线程时,f.FullName 一直递增到数组的末尾。

    无论如何,为什么要在这里迭代两次?

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

    但是,这仍然是错误的,而且可能更糟,因为您现在有一个竞争条件来查看哪个线程运行得更快:写入控制台项或迭代到下一个 FileInfo。

    【讨论】:

    • 在这段代码中,线程在主线程完成修改之前访问 f 变量。
    【解决方案4】:

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

    改为在迭代中启动线程......

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

    我认为这里不可能发生竞争,因为所有线程都无法访问共享内存。

    【讨论】:

    • 存在竞争条件,f 是共享的。您可以通过让每个线程休眠几毫秒来测试它: Thread t = new Thread(delegate() { Thread.Sleep(300); Console.WriteLine(f.FullName); });
    • f 是共享的,但字符串 filName 不是......它是一个堆栈帧变量,将特定于每个线程......
    • 为了竞争,必须有一个共享内存变量,其值是可变的,并且在处理的某些部分是错误的......字符串文件名在线程之前从 f.FullNam 初始化创建,它的值存储在线程特定的堆栈帧中
    【解决方案5】:

    以下也可以。

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

    【讨论】:

    • 我认为这行不通。 f 仍然在循环外声明并在匿名委托中访问
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多