Reputation: 175
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
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
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
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
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