С# 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' не получает правильную настройку в анонимном методе?
Ответы
Ответ 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);
});