Brondahl
Brondahl

Reputation: 8597

Add further tasks to an existing WaitAll

Supposing I have a collection of Tasks, which I'm going to WaitAll() on. Suppose that, before they've all finished, I want to add some more tasks to that collection and I want the wait to continuing waiting until they're all done too. And I might add still more tasks before the end, etc. etc.

Can I do that using TPL? Or am I going to have to hand-roll my own thread management? (yuck!)

WaitAll() acts on Task[] so I can't just have a List<Task> that I call .ToArray() on, because then the Wait won't know about any new Tasks that are added?

Similar questions about WaitAny() apply.

Upvotes: 2

Views: 561

Answers (2)

Negative Eddy
Negative Eddy

Reputation: 401

If you want to synchronously wait on something and then wait on something else later, you need to cancel the original wait and start a new one. Do the original wait on what you have at the beginning, then when you need to add more tasks, add them to the current task list and signal the cancellation which restarts the wait.

public class MutableTaskWaiter
{
    private List<Task> _tasks = new List<Task>();
    private CancellationTokenSource _cts;

    public IEnumerable<Task> Tasks
    {
        get
        {
            lock (_tasks)
            {
                return _tasks.ToArray();
            }
        }
    }

    public void WaitAll(IEnumerable<Task> tasks)
    {
        WaitMoreTasks(tasks);

        do
        {
            try
            {
                _cts = new CancellationTokenSource();
                Task.WaitAll(_tasks.ToArray(), _cts.Token);
            }
            catch (OperationCanceledException)
            {
                // start over and wait for new tasks
            }
        }
        while (_cts.IsCancellationRequested);
    }


    public void WaitAny(IEnumerable<Task> tasks)
    {
        WaitMoreTasks(tasks);

        do
        {
            try
            {
                _cts = new CancellationTokenSource();
                Task.WaitAny(_tasks.ToArray(), _cts.Token);
            }
            catch (OperationCanceledException)
            {
                // start over and wait for new tasks
            }
        }
        while (_cts.IsCancellationRequested);
    }


    public void WaitMoreTasks(IEnumerable<Task> tasks)
    {
        lock (_tasks)
        {
            _tasks.AddRange(tasks);
            if (_cts != null)
            {
                // signal the wait to restart with the updated task list
                _cts.Cancel();
            }
        }
    }
}

Of course you are still going to have to deal with race conditions in the WaitAll scenarithat come up if you are adding Tasks for a long time and you have some short lived tasks originally. e.g. if my intial task list completes after 5 seconds, I cant add a new task to the wait list after 10 seconds because I'll already be done waiting.

Upvotes: 0

Yacoub Massad
Yacoub Massad

Reputation: 27871

Here is one solution for WaitAll:

Assume that you have the following list to hold the tasks:

List<Task> tasks = new List<Task>();

Here is how you can wait on them:

while (true)
{
    Task[] my_tasks;

    lock (tasks)
    {
        my_tasks = tasks.ToArray(); //take snapshot
        tasks.Clear(); //clear list
    }

    if (my_tasks.Length == 0)
        break;

    Task.WaitAll(my_tasks);
}

Just make sure that you lock on the list when you add tasks to the list like this:

lock (tasks)
{
    tasks.Add(...
}

By the way, is there a reason why you are synchronously waiting for the tasks instead of asynchronously (you are using WaitAll and not WhenAll)?

Upvotes: 1

Related Questions