Как удалить задачу из коллекции после завершения - PullRequest
6 голосов
/ 17 мая 2011

Допустим, у меня есть коллекция 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 обрезать.

Один из подходов, которые я рассмотрел, - это дать Task доступ к коллекции и удалить ее в самом конце.Но я также смотрю на архитектуру, где мне пришлось бы удалить задачу, которую мой компонент не создал.Моя первая мысль - прикрепить триггер или событие к завершению каждой Задачи, что-то вроде этого:

foreach (Task task in createdSomewhereElse)
{
    lock (myTasks) myTasks.Add(task);
    task.WhenTaskIsCompleted += 
        (o, ea) => { lock(myTasks) myTasks.Remove(task); };
    task.Start();
}

... но Task не имеет такого события.Есть ли хороший способ выполнить то, что я ищу?Примерно так:

Ответы [ 3 ]

14 голосов
/ 17 мая 2011

Вы, конечно, можете прикрепить триггер для завершения задачи: Task.ContinueWith (и его общий эквивалент).Это, вероятно, было бы достаточно для вас.

Вы можете также захотите использовать ConcurrentDictionary как своего рода параллельный набор бедняков - таким образом, вам не придется блокироваться, когдадоступ к коллекции.Просто используйте свойство Keys при итерации и используйте все что угодно в качестве значения.

2 голосов
/ 22 мая 2011

Зачем вам нужно хранить задачи в коллекции?

Почему бы не использовать решение, основанное на 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 голос
/ 17 мая 2011

Используйте ContinueWith , чтобы установить действие, которое удаляет задачу из набора.

...