Stephen Ross
Stephen Ross

Reputation: 892

Chain ContinueWith Tasks at runtime

I'm attempting to create a helper method for my view models which takes a collection of actions to perform and will block other collection of actions occurring at the same time.

I believe that this is a prime candidate to use Task.ContinueWith and my original hardcoded functionality worked without issue using this method but when I attempted to build the actions at runtime I'm hitting an issue in that my Tasks are running in different orders than I would expect.

My class looks like:

internal abstract class ViewModel : IViewModel
{
    private readonly AutoResetEvent autoResetEvent = new AutoResetEvent(true);

    protected Task SynchronousTask(IList<Action> actions)
    {
        var initialTask = new Task(() => this.autoResetEvent.WaitOne());

        for (var i = 0; i < actions.Count; i++)
        {
            var i1 = i;
            initialTask.ContinueWith(task => actions[i1]());
        }

        initialTask.ContinueWith(task => this.autoResetEvent.Set());

        initialTask.Start();
        return initialTask;
    }
}

Essentially I want to wait on the AutoResetEvent blocking any new tasks running until this has been set. I then loop through each of the tasks adding them as continuations of the initial task and finally ending it with a continuation to set the AutoResetEvent.

In theory to me this looks like it should work, but when I run it I end up with the continuations running in the incorrect order.

I understand that there are other ways that I could do this without using the ContinueWiths but I'd like to see where and how I went wrong using this method.

Upvotes: 1

Views: 882

Answers (2)

one_mile_run
one_mile_run

Reputation: 3414

All you need is just chain to the last task, not initial task. Imagine hooking next task to the previous one, instead of hooking all tasks to the first one.

public static Task SynchronousTask(List<Action> actions)
{
    var initialTask = new Task(() => autoResetEvent.WaitOne());

    Task lastTask = initialTask;
    for (int i = 0; i < actions.Count; i++)
    {
        int i1 = i;
        lastTask = lastTask.ContinueWith(task => actions[i1]()); // here
    }

    lastTask.ContinueWith(task => autoResetEvent.Set());

    initialTask.Start();
    return initialTask;
}

Upvotes: 2

Evk
Evk

Reputation: 101553

When you add multiple continuations to the same task, like in your example - they are started in certain order (as far as I know, that order is not documented and you should not rely on it - but it seems they are started in last in first out order), but they do not run in particular order. After they are started - they all run in parallel. So in your case, all actions and autoResetEvent.Set() will execute almost at the same time after initialTask has completed. If you need sequential execution - just put all that code in one Task and run it, no need to use ContinueWith here. If you really want to use ContinueWith - chain continuations (call ContinueWith on result of previous ContinueWith).

Upvotes: 5

Related Questions