C # 2.0 Threading Question (анонимные методы) - PullRequest
12 голосов
/ 30 октября 2008

У меня есть простое приложение со следующим кодом:

   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' файла неправильно настроена в анонимном методе?

Ответы [ 5 ]

11 голосов
/ 30 октября 2008

Анонимный метод сохраняет ссылку на переменную во включающем блоке, а не на фактическое значение переменной.

К тому моменту, когда методы фактически выполняются (когда вы запускаете потоки), f назначается для указания на последнее значение в коллекции, поэтому все 3 потока выводят это последнее значение.

6 голосов
/ 30 октября 2008

Вот несколько хороших статей об анонимных методах в 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);
   }

это будет работать так, как вы хотели.

0 голосов
/ 30 октября 2008

Следующее также будет работать.

    Thread t = new Thread(delegate()
    {
        string name = f.Name;
        Console.WriteLine(name);
    });
0 голосов
/ 30 октября 2008

Это потому, что базовый код для итератора (foreach) уже «перебрал» все значения в списке до запуска потоков ... Поэтому, когда они запускаются, значение, на которое указывает итератор, является последним в списке ...

Вместо этого запускайте поток внутри итерации ....

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

Я не верю, что здесь возможна гонка, так как нет общей памяти, доступной из всех потоков.

0 голосов
/ 30 октября 2008

Это потому, что f.FullName является ссылкой на переменную, а не на значение (как вы пытались его использовать). К тому времени, когда вы на самом деле запускаете потоки, f.FullName увеличивалось до самого конца массива.

В любом случае, зачем перебирать вещи здесь дважды?

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

Однако, это все еще неправильно, и, возможно, еще хуже, поскольку теперь у вас есть условие гонки, чтобы увидеть, какой поток идет быстрее: запись элемента консоли или итерация к следующему FileInfo.

...