1

Currently I have a code like this:

 bool task1Result = await RunTask1(data);
 if(!task1Result)
     return false;

 bool task2Result = await RunTask2(data);
 if(!task2Result)
     return false;

 bool task3Result = await RunTask3(data);
 if(!task3Result)
     return false;

 bool task4Result = await RunTask4(data);
 if(!task4Result)
     return false;

Added sample:

private async Task<bool> RunListOfTasks() {
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;

var tasks = new List<Task<bool>> { RunTask1(data, ct), RunTask2(data, ct), RunTask3(data, ct), RunTask4(data, ct) };
while (tasks.Any())
{
    var currentTask = await Task.WhenAny(tasks);
    if (!await currentTask)
    {
        ct.Cancel();
        return false;
    }
    tasks.Remove(currentTask);
}
return true;
}

Is it possible to run all of them in parallel and if one of them fails (like result is false), then stop processing the rest and return. Thanks

6
  • 1
    Each run task method would need to be changed to be cancellable e.g. by accepting a CancellationToken. Then you could use Task.WhenAll to parallelise. Commented Jun 1, 2022 at 9:33
  • Tasks aren't threads. You don't cancel the tasks, you cancel the actions/jobs that are executed by those tasks. Your methods need to accept a CancellationToken parameter and check it to see if cancellation was signalled Commented Jun 1, 2022 at 9:34
  • if one of them fails (like result is false), that's not what fail means. A false result means the task succeeded and returned false. Failure means that an exception was thrown. You can catch any failure easily if you use a try block around await Task.WhenAll(); and signal the other methods to cancel through a CancellationTokenSource. It's a lot harder to do the same if you have to check every result. You won't be able to use Task.WhenAll in that case Commented Jun 1, 2022 at 9:38
  • What do those methods really do? Why do they return a bool instead of throwing? It matters. If they have to return a boolean, you should probably pass a CancellationTokenSource to each of them, to allow each task to signal cancellation before returning. Typically, cancellation flows from the outside in Commented Jun 1, 2022 at 9:40
  • yes, fail means that the task succeeded and the output of the task is false. Commented Jun 1, 2022 at 9:50

2 Answers 2

2

The Task.WhenAny-in-a-loop is generally considered an antipattern, because of its O(n²) complexity. The preferred approach is to wrap your tasks in another set of tasks, that will include the functionality of canceling the CancellationTokenSource when the result is false. Then await the wrapper tasks instead of the initial tasks, and propagate their result.

An easy way to wrap the tasks is the Select LINQ operator:

private async Task<bool> RunListOfTasks()
{
    using CancellationTokenSource cts = new();

    List<Task<bool>> tasks = new()
    {
        RunTask1(data, cts.Token),
        RunTask2(data, cts.Token),
        RunTask3(data, cts.Token),
        RunTask4(data, cts.Token),
    };

    Task<bool>[] enhancedTasks = tasks.Select(async task =>
    {
        try
        {
            bool result = await task.ConfigureAwait(false);
            if (!result) cts.Cancel();
            return result;
        }
        catch (OperationCanceledException) when (cts.IsCancellationRequested)
        {
            return false;
        }
    }).ToArray();

    bool[] results = await Task.WhenAll(enhancedTasks).ConfigureAwait(false);
    return results.All(x => x);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Very nice mini setup.
1

Microsoft's Reactive Framework does this in a very nice way:

bool[] result =
    await 
        Observable
            .Merge(
                Observable.FromAsync(ct => RunTask1(data, ct)),
                Observable.FromAsync(ct => RunTask2(data, ct)),
                Observable.FromAsync(ct => RunTask3(data, ct)),
                Observable.FromAsync(ct => RunTask4(data, ct)))
            .TakeUntil(x => x == false)
            .ToArray();

It returns all of the results that come in right up to the point one of the tasks returns a false.

11 Comments

This is a decent solution, with the caveat that the await might return before all started RunTaskX operations have completed. In other words it allows fire-and-forget to occur. Here is a question related to this issue.
@TheodorZoulias - No, depending on how each task is implemented, the fact that the observable stops, then the tasks stop proactively.
Do you mean that fire-and-forget is impossible with the answer above? I am just claiming is that fire-and-forget is possible, not that it will happen always. And depending on the scenario, fire-and-forget might be anything from OK to unacceptable.
It depends on how you write the task methods as to if they fire and forget or if they terminate.
@TheodorZoulias - Save Rx for the priesthood...
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.