Vineet v
Vineet v

Reputation: 175

Invoking multiple tasks

I've a C# application in which I need to invoke four asynchronous tasks(which internally call a third-party webservice). Each task returns a boolean value true/false depending upon success or failure.

I need to invoke another method say PostProcessing() once any of these 4 tasks return true. For e.g. if #2 method call returns true, I need to abort processing and invoke PostProcessing() method.

If all tasks return false, I dont want to invoke PostProcessing().

What's the best way to implement this approach please?Is it Task.ContinueWith()??

Thanks.

Upvotes: 3

Views: 154

Answers (4)

Thomas Levesque
Thomas Levesque

Reputation: 292425

I would do something like this:

var cancellationSource = new CancellationTokenSource();

// Start the tasks
var tasks = new List<Task<bool>>
{
    WebService(cancellationSource.Token),
    WebService(cancellationSource.Token),
    WebService(cancellationSource.Token),
    WebService(cancellationSource.Token)
};

// Wait until first task returns true
bool success = false;
while (!success && tasks.Any())
{
    var completedTask = await Task.WhenAny(tasks);
    try
    {
        // It either completed or failed, try to get the result
        success = await completedTask;
    }
    catch (Exception ex)
    {
        // ignore or log exception
    }
    tasks.Remove(completedTask);
}

// Cancel remaining tasks
cancellationSource.Cancel();

// Ensure all tasks have completed to avoid unobserved exceptions
if (tasks.Any())
{
    try
    {           
        await Task.WhenAll(tasks);
    }
    catch (Exception ex)
    {
        // ignore or log exception
    }
}

if (success)
{
    PostProcessing();
}

Not as fancy as Enigmativity's solution, but straightforward ;)

Upvotes: 0

Jason Boyd
Jason Boyd

Reputation: 7029

Personally, I would probably use Reactive Extensions (like @Enigmativity) mentioned. If you want to avoid using Reactive Extensions, though, then I think you could combine Task.WhenAny with a while loop. If I were to go this route I would create a static method to keep things clean. So something like this:

public static class TaskExtensions
{
    public static async Task<Task<TResult>> WhenAnyWithPredicate<TResult>(Func<Task<TResult>, bool> predicate, params Task<TResult>[] tasks)
    {
        if (tasks == null)
        {
            throw new ArgumentNullException();
        }

        if (tasks.Length == 0)
        {
            return null;    
        }

        // Make a safe copy (in case the original array is modified while we are awaiting).
        tasks = tasks.ToArray();

        // Await the first task.
        var result = await Task.WhenAny(tasks);

        // Test the task and await the next task if necessary.
        while (tasks.Length > 0 && !predicate(result))
        {
            tasks = tasks.Where(x => x != result).ToArray();
            if (tasks.Length == 0)
            {
                result = null;
            }
            else
            {
                result = await Task.WhenAny(tasks);
            }
        }

        // Return the result.
        return result;
    }
}

You would use it like this.

CancellationTokenSource cts = new CancellationTokenSource();

// Start your four tasks.
var tasks = new Task<bool>[]
{
    CreateTask1WithCancellationToken(cts.Token),
    CreateTask2WithCancellationToken(cts.Token),
    CreateTask3WithCancellationToken(cts.Token),
    CreateTask4WithCancellationToken(cts.Token),
}

// Wait for the first task with a result of 'true' to complete.
var result = await TaskExtensions.WhenAnyWithPredicate(x => x.Status == TaskStatus.RanToCompletion && x.Result, tasks);

// Cancel the remaining tasks (if any).
cts.Cancel();

// If you have a nonnull task then success!
if (result != null)
{
    PostProcessing();
}

Upvotes: 1

Enigmativity
Enigmativity

Reputation: 117055

I would use Microsoft's Reactive Framework (Rx) for this - it becomes drop dead easy.

To start with I'll assume that the signatures of the methods you're working with are these:

public async Task<bool> WebService1()
public async Task<bool> WebService2()
public async Task<bool> WebService3()
public async Task<bool> WebService4()
public void PostProcessing()

Now you can set this use Rx like this:

var webservices = new Func<Task<bool>>[]
{
    WebService1, WebService2, WebService3, WebService4,
};

IObservable<bool> query =
    webservices
        .ToObservable()
        .SelectMany(ws => Observable.FromAsync(ws))
        .Where(b => b == true)
        .Take(1);

IDisposable subscription = query.Subscribe(b => PostProcessing());

This nicely calls all four web asynchronous services (Observable.FromAsync(ws)) asynchronously .ToObservable().SelectMany(...) and then filters the result to only those that return true (.Where(b => b == true)). It finally only wants one result .Take(1).

Then, if it does get one result - which must be true - it then calls PostProcessing when that occurs.

If you need to abort before any web service returns you can always call subscription.Dispose().

Simple.

Upvotes: 3

Joakim Hansson
Joakim Hansson

Reputation: 544

I'm on my cell phone so will be hard for me to write any code. But from my understanding cancellation token is what you want. Please have a look here https://msdn.microsoft.com/en-us/library/system.threading.cancellationtoken(v=vs.110).aspx

You can then register a callback for the cancellation request where you invoke your method. Please have a look at the example https://msdn.microsoft.com/en-us/library/ee191554(v=vs.110).aspx

Upvotes: -1

Related Questions