Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invoking multiple tasks

Tags:

c#

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.

like image 626
Vineet v Avatar asked Feb 22 '26 00:02

Vineet v


2 Answers

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.

like image 184
Enigmativity Avatar answered Feb 23 '26 14:02

Enigmativity


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();
}
like image 43
Jason Boyd Avatar answered Feb 23 '26 13:02

Jason Boyd



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!