Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove Task from collection after completed

Let's say I have a collection of System.Threading.Tasks.Task:

HashSet<Task> myTasks = new HashSet<Task>();

...and I periodically feed more into the collection as I have more data that needs to be processed:

foreach (DataItem item in itemsToProcess)
    myTasks.Add(
        Task.Factory.StartNew(
            () => Process(item),
            cancellationToken,
            TaskCreationOptions.LongRunning,
            TaskScheduler.Default));    

Since Tasks remain in TaskStatus.RanToCompletion status after finishing instead of just disappearing, they would remain in the collection until explicitly removed and the collection would grow indefinitely. The Tasks need to be pruned to prevent this.

One approach I have looked at is giving the Task access to the collection and having it remove itself at the very end. But I am also looking at an architecture where I would have to remove a Task that my component has not created. My first thought is to attach a trigger or event to completion of each Task, something like this:

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

...but Task has no such event. Is there any good way to accomplish what I'm looking for? Something like this:

like image 506
Calvin Fisher Avatar asked May 17 '11 15:05

Calvin Fisher


People also ask

How to cancel Task after timeout c#?

You can cancel an asynchronous operation after a period of time by using the CancellationTokenSource. CancelAfter method if you don't want to wait for the operation to finish.

How do you mark a task completed in C#?

Run() , Task. Factory. StartNew() or new Task() ). But it doesn't apply to Task s with no delegate, those can be created using async - await or TaskCompletionSource .

How many tasks can be created C#?

The general answer is "Measure, Measure, Measure" :) if you're not experiencing any problems with performance, you shouldn't start optimizing. I'd say 200 tasks are fine though.


3 Answers

You certainly can attach a trigger for when a task is completed: Task.ContinueWith (and its generic equivalent). That would probably be good enough for you.

You may also wish to use ConcurrentDictionary as a sort of poor-man's concurrent set - that way you wouldn't have to lock when accessing the collection. Just use the Keys property when iterating, and use anything you like as the value.

like image 200
Jon Skeet Avatar answered Nov 16 '22 04:11

Jon Skeet


Why do you need to keep the tasks in a collection?

Why not use a solution based on BlockingCollection and Parallel.ForEach

var sources = new BlockingCollection<DataItem>();

Task.Factory.StartNew(() => {
    Parallel.ForEach(sources.GetConsumingPartitioner(),
                     item => Process(item));
});

Now you can just feed your items into the blocking collection and they will automatically be processed.

foreach (DataItem item in itemsToProcess)
    sources.Add(item);

You can use sources.Countand foreach (DataItem item in sources) to see non-processed items. (A difference from your solution is that you can't see items currently processing)

like image 28
adrianm Avatar answered Nov 16 '22 02:11

adrianm


Use the ContinueWith to set a action which removes the task from the set.

like image 23
schlingel Avatar answered Nov 16 '22 03:11

schlingel