#c# #.net-4.0 #task-parallel-library
#c# #.net-4.0 #задача-параллельная-библиотека
Вопрос:
Допустим, у меня есть коллекция System.Threading.Tasks.Task
:
HashSet<Task> myTasks = new HashSet<Task>();
… и я периодически добавляю больше в коллекцию, поскольку у меня есть больше данных, которые необходимо обработать:
foreach (DataItem item in itemsToProcess)
myTasks.Add(
Task.Factory.StartNew(
() => Process(item),
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Default));
Поскольку Task
s остаются в TaskStatus.RanToCompletion
состоянии после завершения, а не просто исчезают, они будут оставаться в коллекции до явного удаления, и коллекция будет расти бесконечно. Task
s необходимо обрезать, чтобы предотвратить это.
Один из подходов, который я рассмотрел, заключается в предоставлении Task
доступа к коллекции и ее удалении в самом конце. Но я также рассматриваю архитектуру, в которой мне пришлось бы удалить задачу, которую не создал мой компонент. Моя первая мысль — прикрепить триггер или событие к завершению каждой задачи, что-то вроде этого:
foreach (Task task in createdSomewhereElse)
{
lock (myTasks) myTasks.Add(task);
task.WhenTaskIsCompleted =
(o, ea) => { lock(myTasks) myTasks.Remove(task); };
task.Start();
}
…но Task
такого события нет. Есть ли какой-нибудь хороший способ выполнить то, что я ищу? Что-то вроде этого:
Ответ №1:
Вы, конечно, можете прикрепить триггер для завершения задачи: Task.ContinueWith
(и его общий эквивалент). Вероятно, этого было бы достаточно для вас.
Возможно, вы также захотите использовать ConcurrentDictionary
как своего рода параллельный набор для бедных — таким образом, вам не придется блокировать доступ к коллекции. Просто используйте Keys
свойство при повторении и используйте все, что вам нравится, в качестве значения.
Комментарии:
1. Джон, ты имеешь в виду конкретную реализацию
ConcurrentHashSet
? Насколько мне известно, в BCL ничего не встроено.2. @LukeH: Doh — Я думал о ConcurrentDictionary. Отредактирует 🙂
3. Похоже, это именно то, что я хочу сделать. 🙂 Итак, замена
=
инструкции наtask.ContinueWith((t) => { lock(myTasks) myTasks.Remove(t); });
правильная? Кроме того, 1 к комментарию @LukeH; Я не видел ни одного из них в системных библиотеках.4. Ааа, на секунду разволновался… На самом деле я использую ConcurrentDictionary в реальном коде, но абстрагировался от HashSet, чтобы уменьшить сложность вопроса.
5. @Calvin: Для меня это выглядит правильно, да. ContinueWith — это одна из тех вещей, на которых построена поддержка асинхронности в C # 5 🙂
Ответ №2:
Почему вам нужно хранить задачи в коллекции?
Почему бы не использовать решение, основанное на BlockingCollection и Parallel.ForEach
var sources = new BlockingCollection<DataItem>();
Task.Factory.StartNew(() => {
Parallel.ForEach(sources.GetConsumingPartitioner(),
item => Process(item));
});
Теперь вы можете просто поместить свои элементы в блокирующую коллекцию, и они будут обработаны автоматически.
foreach (DataItem item in itemsToProcess)
sources.Add(item);
Вы можете использовать sources.Count
и foreach (DataItem item in sources)
для просмотра необработанных элементов.
(Отличие от вашего решения в том, что вы не можете видеть элементы, обрабатываемые в данный момент)
Комментарии:
1. Интересная альтернатива. Я сохраняю задачи в коллекции, чтобы я мог видеть, сколько из них все еще обрабатываются, дождаться завершения всех текущих задач обработки (
Task.WaitAll(myTasks.ToArray());
), а в реальной жизни, когда я использую ConcurrentDictionary, связать элемент данных с задачей, которая его обрабатывает.2. @adrianm, есть ли какое-либо ожидание отмены конкретной задачи?
3. @Calvin, ничего встроенного. Параллельно. ForEach получил некоторые перегрузки, которые позволяют отменить оставшиеся задачи. Я бы добавил флаг или токен отмены в элемент данных и проверил отмену в Process ()
4. Используя Parallel. Выполнение каждого из блокирующих наборов опасно: blogs.msdn.com/b/pfxteam/archive/2010/04/06/9990420.aspx
5. @Ronnie: Если вы прочитаете свою собственную статью по ссылке, вы увидите, что решение заключается в использовании
.GetConsumingPartitioner
вместо.GetConsumingEnumerable
. Затем посмотрите на мой код выше.
Ответ №3:
Используйте ContinueWith, чтобы задать действие, которое удаляет задачу из набора.