Mark Richman
Mark Richman

Reputation: 29710

Dependency and Execution Order in Asynchronous Operations

I am working on an application which performs remote installation of various software components. There exist some component installation order dependencies across machines, and these component installs are all performed asynchronously.

I'm wondering what the best approach is to establishing these component installation order dependencies. For example, I have what I call "Target Machines" A and B, and Components 1 and 2. Here, installing Component 2 on Machine B must wait for the completion of Component 1 on Machine A. This is a very simple example, but I want to establish a framework so that more complex scenarios can be easily implemented.

My current logic is as follows:

foreach (var tm in TargetMachines)
{
    IProgress<InstallProgress> p = tm.Progress;

    // Get installer module for this target machine
    var installModule =
        ServiceLocator.Default.GetAllInstances<IInstallModule>()
            .FirstOrDefault(m => m.ProductFamily.Equals(GetCurrentProductFamily()));

    // Add the Install() task to the queue
    if (installModule != null)
        installationTasks.Add(installModule.Install(tm.MachineName, p));
}

Here, I'm simply iterating through each available installModule for each Target Machine and running its Install() method. My concrete InstallModule implementations are responsible for invoking the Component installation in a prescribed (currently hardcoded) order.

You can think of two concurrent timelines:

[Target Machine A] === Component 1 ===> Finished

[Target Machine B] === Component 2 [Wait on Target Machine A, Component 1] ===> Finished

As evidenced from this post's title, I saw a similar feature in Dynamics CRM's docs, but nothing the .NET BCL which looks appropriate.

I understand that I have the option to use ContinueWith, such that I can do taskA.ContinueWith(taskB). However, I am primarily interested in how to represent these dependencies in such a way that I can construct the task chain from it.

I also found this article which implements a DependencyManager but predates the TPL. Not sure if this is a best-practices solution five years hence.

EDIT 1: Potential solution here from Microsoft, the "Task Graph Pattern".

EDIT 2: Per svick's suggestion, I am experimenting with this:

public class Component
{
    public Component()
    {
        Dependencies = new List<Component>();
    }

    public IEnumerable<Component> Dependencies { get; set; }

    public Task<ExecutionResult> InstallationCompletion { get; set; }

    public async Task<ExecutionResult> InstallAsync()
    {
        // http://stackoverflow.com/questions/25385129/dependency-and-execution-order-in-asynchronous-operations

        await Task.WhenAll(Dependencies.Select(c => c.InstallationCompletion));

        // install this component here

        var executionResult = new ExecutionResult(0, "Installation completed");
        return executionResult;
    }
}

Upvotes: 2

Views: 1035

Answers (1)

svick
svick

Reputation: 244787

What I would do is to make each component contain a list of components that it depends on and also expose a Task indicating that installation of that component completed.

You can then do something like:

// there is no non-generic TaskCompletionSource, so use bool
private TaskCompletionSource<bool> installationCompletion
    = new TaskCompletionSource<bool>();

public Task InstallationCompletion { get { return installationCompletion.Task; } }

public async Task InstallAsync()
{
    await Task.WhenAll(this.Dependencies.Select(c => c.InstallationCompletion));

    // install this component here

    installationCompletion.SetResult(true);
}

(Possibly also using inheritance to keep the common code in one place only.)

Then you can start the installation of all components at the same time, the dependencies will take care of themselves thanks to the above code.

await Task.WhenAll(allComponents.Select(c => c.InstallAsync()));

If you can't use C# 5.0, you can use Task.Factory.ContinueWhenAll() for the same purpose as await Task.WhenAll(), only with worse syntax.

Upvotes: 3

Related Questions