HiveBrain v1.2.0
Get Started
← Back to all entries
principlecsharpMinor

Correct approach to wait for multiple async methods to complete

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
waitasyncmethodscompleteforcorrectmultipleapproach

Problem

I have an IWorkflow interface defined as follows:

public interface IWorkflow
{
    Task ConfigureAsync();
    Task StartAsync();
    Task StopAsync();
}


And I have an Engine class:

public sealed class Engine : IEngine
{
    private readonly List workflows = new List();

    public Engine(IEnumerable workflows)
    {
        this.workflows.AddRange(workflows);
    }

    public void Start()
    {
        var configureTasks = this.workflows.Select(w => w.ConfigureAsync()).ToArray();
        Task.WaitAll(configureTasks);

        var startTasks = this.workflows.Select(w => w.StartAsync()).ToArray();
        Task.WaitAll(startTasks);
    }

    public void Stop()
    {
        var stopTasks = this.workflows.Select(w => w.StopAsync()).ToArray();
        Task.WaitAll(stopTasks);
    }
}


Is this the correct way for the Engine to invoke in parallel the configure method on all workflows and then once all are completed, invoke in parallel the start method on all workflows?

Solution

Your code is absolutely correct in case when you want to start workflows only when all of them are configured.

But if you want to start each workflow once it's configured (independently from other workflows) then it might be a good idea to use continuations... In .NET 4.5 it would look like this:

public sealed class Engine : IEngine
{
    private readonly List _workflows;

    public Engine(IEnumerable workflows)
    {
        _workflows = new List(workflows);
    }

    private async Task RunWorkflow(IWorkflow workflow)
    {
        await workflow.ConfigureAsync();
        await workflow.StartAsync();
    }

    public void Start()
    {
        var startTasks = this._workflows.Select(RunWorkflow).ToArray();
        Task.WaitAll(startTasks);
    }

    public void Stop()
    {
        var stopTasks = _workflows.Select(w => w.StopAsync()).ToArray();
        Task.WaitAll(stopTasks);
    }
}


Also I would suggest to use CancellationToken to stop asynchronous processing.

UPDATE Based on comments it is really needed to wait for all workflows to be configured before starting them. So cancellable implementation can look like this:

public interface IWorkflow
{
    Task ConfigureAsync(CancellationToken token);
    Task StartAsync(CancellationToken token);
}

public sealed class Engine : IEngine
{
    private readonly List _workflows;
    private readonly CancellationTokenSource _cancellationTokenSource;
    private Task _mainTask;

    public Engine(IEnumerable workflows)
    {
        _workflows = new List(workflows);
        _cancellationTokenSource = new CancellationTokenSource();
    }

    private async Task RunWorkflows()
    {
        await Task.WhenAll(_workflows.Select(w => w.ConfigureAsync(_cancellationTokenSource.Token)));
        if (_cancellationTokenSource.IsCancellationRequested)
            return;
        await Task.WhenAll(_workflows.Select(w => w.StartAsync(_cancellationTokenSource.Token)));
    }

    public void Start()
    {
        _mainTask = RunWorkflows();
        _mainTask.Wait();
    }

    public void Stop()
    {
        _cancellationTokenSource.Cancel();
        var mainTask = _mainTask;
        if (mainTask != null)
            mainTask.Wait();
    }
}

Code Snippets

public sealed class Engine : IEngine
{
    private readonly List<IWorkflow> _workflows;

    public Engine(IEnumerable<IWorkflow> workflows)
    {
        _workflows = new List<IWorkflow>(workflows);
    }

    private async Task RunWorkflow(IWorkflow workflow)
    {
        await workflow.ConfigureAsync();
        await workflow.StartAsync();
    }

    public void Start()
    {
        var startTasks = this._workflows.Select(RunWorkflow).ToArray();
        Task.WaitAll(startTasks);
    }

    public void Stop()
    {
        var stopTasks = _workflows.Select(w => w.StopAsync()).ToArray();
        Task.WaitAll(stopTasks);
    }
}
public interface IWorkflow
{
    Task ConfigureAsync(CancellationToken token);
    Task StartAsync(CancellationToken token);
}

public sealed class Engine : IEngine
{
    private readonly List<IWorkflow> _workflows;
    private readonly CancellationTokenSource _cancellationTokenSource;
    private Task _mainTask;

    public Engine(IEnumerable<IWorkflow> workflows)
    {
        _workflows = new List<IWorkflow>(workflows);
        _cancellationTokenSource = new CancellationTokenSource();
    }

    private async Task RunWorkflows()
    {
        await Task.WhenAll(_workflows.Select(w => w.ConfigureAsync(_cancellationTokenSource.Token)));
        if (_cancellationTokenSource.IsCancellationRequested)
            return;
        await Task.WhenAll(_workflows.Select(w => w.StartAsync(_cancellationTokenSource.Token)));
    }

    public void Start()
    {
        _mainTask = RunWorkflows();
        _mainTask.Wait();
    }

    public void Stop()
    {
        _cancellationTokenSource.Cancel();
        var mainTask = _mainTask;
        if (mainTask != null)
            mainTask.Wait();
    }
}

Context

StackExchange Code Review Q#19285, answer score: 8

Revisions (0)

No revisions yet.