Запуск задач в foreach Loop использует значение последнего элемента
Я делаю первую попытку играть с новыми Задачами, но что-то происходит, чего я не понимаю.
Во-первых, код, который довольно прямолинейный. Я передаю список путей к некоторым файлам изображений и пытаюсь добавить задачу для обработки каждого из них:
public Boolean AddPictures(IList paths) { Boolean result = (paths.Count > 0); List tasks = new List(paths.Count); foreach (string path in paths) { var task = Task.Factory.StartNew(() => { Boolean taskResult = ProcessPicture(path); return taskResult; }); task.ContinueWith(t => result &= t.Result); tasks.Add(task); } Task.WaitAll(tasks.ToArray()); return result; }
Я обнаружил, что если я просто позволю этому запустить, скажем, список из трех путей в модульном тесте, все три задачи используют последний путь в предоставленном списке. Если я пройду (и замедляю обработку цикла), используется каждый путь из цикла.
- Python 2.7: streamовый HTTP-сервер, поддерживающий несколько соединений на одном порту
- Сколько streamов слишком много?
- Как прервать Console.ReadLine
- Многопоточность имеет смысл для операций с привязкой к IO?
- Очередь процесса с многопоточным или задачами
Может кто-нибудь объяснить, что происходит, и почему? Возможные обходные пути?
- Можно ли использовать мьютекс в многопроцессорном случае в Linux / UNIX?
- ключевое слово блокировки в C #
- Как правильно прочитать поле Interlocked.Increment' int?
- Почему в OpenMP запрещен оператор! =?
- Надуть представление в фоновом streamе
- Безопасно ли использовать логический флаг, чтобы остановить stream из C #
- В чем разница между атомными / летучими / синхронизированными?
- Doxygen медленный
Вы закрываете переменную цикла. Не делай этого. Вместо этого возьмите копию:
foreach (string path in paths) { string pathCopy = path; var task = Task.Factory.StartNew(() => { Boolean taskResult = ProcessPicture(pathCopy); return taskResult; }); task.ContinueWith(t => result &= t.Result); tasks.Add(task); }
Ваш текущий код захватывает path
не его значение при создании задачи, но сама переменная. Эта переменная меняет значение каждый раз, когда вы проходите цикл, поэтому он может легко измениться к моменту, когда вы вызываете делегата.
Принимая копию переменной, вы вводите новую переменную каждый раз, когда вы проходите цикл – когда вы фиксируете эту переменную, она не будет изменена в следующей итерации цикла.
У Эрика Липперта есть пара сообщений в блогах, которые идут в этом намного подробнее: часть 1 ; часть 2 .
Не чувствую себя плохо – это ловит почти всех 🙁
Лямбда, которую вы передаете в StartNew
, ссылается на переменную path
, которая изменяется на каждой итерации (т. StartNew
Ваша lambda использует ссылку path
, а не только ее значение). Вы можете создать локальную копию, чтобы не указывать на версию, которая изменится:
foreach (string path in paths) { var lambdaPath = path; var task = Task.Factory.StartNew(() => { Boolean taskResult = ProcessPicture(lambdaPath); return taskResult; }); task.ContinueWith(t => result &= t.Result); tasks.Add(task); }