Подтвердить что ты не робот

С# 2.0 Threading Question (анонимные методы)

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

   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 потока, причем каждый поток печатает одно из имен файлов; однако вместо этого каждый поток будет печатать имя последнего файла в массиве "файлы".

Почему это? Почему текущая переменная 'f' не получает правильную настройку в анонимном методе?

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 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.

Ответ 4

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

Начните поток внутри итерации вместо этого.

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

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

Ответ 5

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

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