Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WaitAll for Changing List<Task>

Updated to explain things more clearly

I've got an application that runs a number of tasks. Some are created initially and other can be added later. I need need a programming structure that will wait on all the tasks to complete. Once the all the tasks complete some other code should run that cleans things up and does some final processing of data generated by the other tasks.

I've come up with a way to do this, but wouldn't call it elegant. So I'm looking to see if there is a better way.

What I do is keep a list of the tasks in a ConcurrentBag (a thread safe collection). At the start of the process I create and add some tasks to the ConcurrentBag. As the process does its thing if a new task is created that also needs to finish before the final steps I also add it to the ConcurrentBag.

Task.Wait accepts an array of Tasks as its argument. I can convert the ConcurrentBag into an array, but that array won't include any Tasks added to the Bag after Task.Wait was called.

So I have a two step wait process in a do while loop. In the body of the loop I do a simple Task.Wait on the array generated from the Bag. When it completes it means all the original tasks are done. Then in the while test I do a quick 1 millisecond test of a new array generated from the ConcurrentBag. If no new tasks were added, or any new tasks also completed it will return true, so the not condition exits the loop.

If it returns false (because a new task was added that didn't complete) we go back and do a non-timed Task.Wait. Then rinse and repeat until all new and old tasks are done.

// defined on the class, perhaps they should be properties
CancellationTokenSource Source = new CancellationTokenSource();
CancellationToken Token = Source.Token;
ConcurrentBag<Task> ToDoList = new ConcurrentBag<Task>();


public void RunAndWait() {
    // start some tasks add them to the list
    for (int i = 0; i < 12; i++)
    {
        Task task = new Task(() => SillyExample(Token), Token);
        ToDoList.Add(task); 
        task.Start();
    }

    // now wait for those task, and any other tasks added to ToDoList to complete
    try
    {
        do 
        {
            Task.WaitAll(ToDoList.ToArray(), Token);
        } while (! Task.WaitAll(ToDoList.ToArray(), 1, Token));
    }
    catch (OperationCanceledException e)
    {
        // any special handling of cancel we might want to do
    }

    // code that should only run after all tasks complete
}

Is there a more elegant way to do this?

like image 703
AgnosticOracle Avatar asked Aug 16 '26 09:08

AgnosticOracle


1 Answers

I'd recommend using a ConcurrentQueue and removing items as you wait for them. Due to the first-in-first-out nature of queues, if you get to the point where there's nothing left in the queue, you know that you've waited for all the tasks that have been added up to that point.

ConcurrentQueue<Task> ToDoQueue = new ConcurrentQueue<Task>();

...

    while(ToDoQueue.Count > 0 && !Token.IsCancellationRequested) 
    {
        Task task;
        if(ToDoQueue.TryDequeue(out task))
        {
            task.Wait(Token);
        }
    }        
like image 186
StriplingWarrior Avatar answered Aug 18 '26 00:08

StriplingWarrior



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!