C # 2.0 Threading Question (анонимные методы)
-
05-07-2019 - |
Вопрос
У меня есть простое приложение со следующим кодом:
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);
});