Вопрос

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

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

Это было полезно?

Решение

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

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

Другие советы

Вот несколько хороших статей об анонимных методах в C # и коде, который будет сгенерирован компилятором:

http://blogs.msdn.com/oldnewthing/ Архив / 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);
   }

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

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

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

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

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

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

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

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

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

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

    Thread t = new Thread(delegate()
    {
        string name = f.Name;
        Console.WriteLine(name);
    });
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top