C# async within an action - c#

I would like to write a method which accept several parameters, including an action and a retry amount and invoke it.
So I have this code:
public static IEnumerable<Task> RunWithRetries<T>(List<T> source, int threads, Func<T, Task<bool>> action, int retries, string method)
{
object lockObj = new object();
int index = 0;
return new Action(async () =>
{
while (true)
{
T item;
lock (lockObj)
{
if (index < source.Count)
{
item = source[index];
index++;
}
else
break;
}
int retry = retries;
while (retry > 0)
{
try
{
bool res = await action(item);
if (res)
retry = -1;
else
//sleep if not success..
Thread.Sleep(200);
}
catch (Exception e)
{
LoggerAgent.LogException(e, method);
}
finally
{
retry--;
}
}
}
}).RunParallel(threads);
}
RunParallel is an extention method for Action, its look like this:
public static IEnumerable<Task> RunParallel(this Action action, int amount)
{
List<Task> tasks = new List<Task>();
for (int i = 0; i < amount; i++)
{
Task task = Task.Factory.StartNew(action);
tasks.Add(task);
}
return tasks;
}
Now, the issue: The thread is just disappearing or collapsing without waiting for the action to finish.
I wrote this example code:
private static async Task ex()
{
List<int> ints = new List<int>();
for (int i = 0; i < 1000; i++)
{
ints.Add(i);
}
var tasks = RetryComponent.RunWithRetries(ints, 100, async (num) =>
{
try
{
List<string> test = await fetchSmthFromDb();
Console.WriteLine("#" + num + " " + test[0]);
return test[0] == "test";
}
catch (Exception e)
{
Console.WriteLine(e.StackTrace);
return false;
}
}, 5, "test");
await Task.WhenAll(tasks);
}
The fetchSmthFromDb is a simple Task> which fetches something from the db and works perfectly fine when invoked outside of this example.
Whenever the List<string> test = await fetchSmthFromDb(); row is invoked, the thread seems to be closing and the Console.WriteLine("#" + num + " " + test[0]); not even being triggered, also when debugging the breakpoint never hit.
The Final Working Code
private static async Task DoWithRetries(Func<Task> action, int retryCount, string method)
{
while (true)
{
try
{
await action();
break;
}
catch (Exception e)
{
LoggerAgent.LogException(e, method);
}
if (retryCount <= 0)
break;
retryCount--;
await Task.Delay(200);
};
}
public static async Task RunWithRetries<T>(List<T> source, int threads, Func<T, Task<bool>> action, int retries, string method)
{
Func<T, Task> newAction = async (item) =>
{
await DoWithRetries(async ()=>
{
await action(item);
}, retries, method);
};
await source.ParallelForEachAsync(newAction, threads);
}

The problem is in this line:
return new Action(async () => ...
You start an async operation with the async lambda, but don't return a task to await on. I.e. it runs on worker threads, but you'll never find out when it's done. And your program terminates before the async operation is complete -that's why you don't see any output.
It needs to be:
return new Func<Task>(async () => ...
UPDATE
First, you need to split responsibilities of methods, so you don't mix retry policy (which should not be hardcoded to a check of a boolean result) with running tasks in parallel.
Then, as previously mentioned, you run your while (true) loop 100 times instead of doing things in parallel.
As #MachineLearning pointed out, use Task.Delay instead of Thread.Sleep.
Overall, your solution looks like this:
using System.Collections.Async;
static async Task DoWithRetries(Func<Task> action, int retryCount, string method)
{
while (true)
{
try
{
await action();
break;
}
catch (Exception e)
{
LoggerAgent.LogException(e, method);
}
if (retryCount <= 0)
break;
retryCount--;
await Task.Delay(millisecondsDelay: 200);
};
}
static async Task Example()
{
List<int> ints = new List<int>();
for (int i = 0; i < 1000; i++)
ints.Add(i);
Func<int, Task> actionOnItem =
async item =>
{
await DoWithRetries(async () =>
{
List<string> test = await fetchSmthFromDb();
Console.WriteLine("#" + item + " " + test[0]);
if (test[0] != "test")
throw new InvalidOperationException("unexpected result"); // will be re-tried
},
retryCount: 5,
method: "test");
};
await ints.ParallelForEachAsync(actionOnItem, maxDegreeOfParalellism: 100);
}
You need to use the AsyncEnumerator NuGet Package in order to use the ParallelForEachAsync extension method from the System.Collections.Async namespace.

Besides the final complete reengineering, I think it's very important to underline what was really wrong with the original code.
0) First of all, as #Serge Semenov immediately pointed out, Action has to be replaced with
Func<Task>
But there are still other two essential changes.
1) With an async delegate as argument it is necessary to use the more recent Task.Run instead of the older pattern new TaskFactory.StartNew (or otherwise you have to add Unwrap() explicitly)
2) Moreover the ex() method can't be async since Task.WhenAll must be waited with Wait() and without await.
At that point, even though there are logical errors that need reengineering, from a pure technical standpoint it does work and the output is produced.
A test is available online: http://rextester.com/HMMI93124

Related

How to close all tasks if at least one is over

There is a decimal parameter. Suppose it is equal to 100. There is a Task that reduces it by 0.1 every 100ms. As soon as the parameter becomes equal to 1, the task should end and the parameter should not decrease any more. Works without problems if there is only one Task. But if there are 2, 3, 100... then the parameter will eventually become less than 1. I try to use CancellationToken to end all tasks, but the result is still the same. My code:
class Program
{
static decimal param = 100;
static CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
static CancellationToken token;
static void Main(string[] args)
{
int tasksCount = 16;
token = cancelTokenSource.Token;
Console.WriteLine("Start Param = {0}", param);
Console.WriteLine("Tasks Count = {0}", tasksCount);
var tasksList = new List<Task>();
for (var i = 0; i < tasksCount; i++)
{
Task task = new Task(Decrementation, token);
tasksList.Add(task);
}
tasksList.ForEach(x => x.Start());
Task.WaitAny(tasksList.ToArray());
Console.WriteLine("Result = {0}", param);
Console.Read();
}
private static void Decrementation()
{
while (true)
{
if (token.IsCancellationRequested)
{
break;
}
if (CanTakeMore())
{
Task.Delay(100);
param = param - 0.1m;
}
else
{
cancelTokenSource.Cancel();
return;
}
}
}
private static bool CanTakeMore()
{
if (param > 1)
{
return true;
}
else
{
return false;
}
}
}
The output is different, but it is always less than 1. How to fix ?
Your tasks are checking and modifying the same shared value in parallel, to some degree as allowed by your CPU architecture and/or Operating System.
Any number of your tasks can encounter a CanTakeMore() result of true "at the same time" (when they call it with the shared value being 1.1m), and then all of them that received true from that call will proceed to decrease the shared value.
This problem can usually be avoided by using a lock statement:
private static object _lockObj = new object();
private static void Decrementation()
{
while (true)
{
if (token.IsCancellationRequested)
{
break;
}
lock (_lockObj)
{
if (CanTakeMore())
{
Task.Delay(100); // Note: this needs an `await`, but the method we're in is NOT `async`...!
param = param - 0.1m;
}
else
{
cancelTokenSource.Cancel();
return;
}
}
}
}
Here is a working .NET Fiddle: https://dotnetfiddle.net/BA6tHX
While your code has other issues (such as the fact that you must await Task.Delay), the fundamental problem is that you must either lock your entire read/write operation, or modify your implementation to enable atomic read and writes.
One option is to take your incoming decimal and convert it to a 32 bit integer, multiplying the param by the number of places of precision you need. In this case, it would be 100 * 10 since you have 1 place of precision.
This enables you to use Thread.VolatileRead in conjunction with Interlocked.CompareExchange to produce the behavior you are looking for (working example).
void Main()
{
int tasksCount = 16;
token = cancelTokenSource.Token;
Console.WriteLine("Start Param = {0}", param);
Console.WriteLine("Tasks Count = {0}", tasksCount);
var tasksList = new List<Task>();
for (var i = 0; i < tasksCount; i++)
{
Task task = new Task(Decrementation, token);
tasksList.Add(task);
}
tasksList.ForEach(x => x.Start());
Task.WaitAny(tasksList.ToArray());
Console.WriteLine("Result = {0}", param);
Console.Read();
}
static int param = 1000;
static CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
static CancellationToken token;
private static void Decrementation()
{
while (true)
{
if (token.IsCancellationRequested)
{
break;
}
int temp = Thread.VolatileRead(ref param);
if (temp == 1)
{
cancelTokenSource.Cancel();
return;
}
int updatedValue = temp - 1;
if (Interlocked.CompareExchange(ref param, updatedValue, temp) == temp)
{
// the update was successful. Delay (or do additional work)
// this still does nothing
// You might want to make your method async or switch to a timer
Task.Delay(100);
}
}
}
The advantage of Thread.VolatileRead + Interlocked.CompareExchange over a straight lock is that if there is any significant work being done in the lock, this approach will perform significantly better. When benchmarked against the following reasonable locking implementation using decimal subtraction:
private static object _locker = new object();
private static decimal param2 = 100.0m;
private static void DecrementationLock()
{
while (true)
{
if (token.IsCancellationRequested)
{
break;
}
lock (_locker)
{
if (param2 > 1)
{
param2 = param2 - 0.1m;
Task.Delay(100);
}
else
{
cancelTokenSource.Cancel();
return;
}
}
}
}
even though the Task.Delay is not awaited in either case, the lock code is over 2.5x slower. That said, in cases where no work is being done, there is essentially no execution time difference between the two approaches.

C# .NET CORE: cancell other tasks if one returned some kind of result

How to cancel all tasks, if one of them return i.e. false (bool) result?
Is it possible to identify which task returned a result?
class Program
{
private static Random _rnd = new Random();
static void Main(string[] args)
{
var tasksCounter = _rnd.Next(4, 7);
var cts = new CancellationTokenSource();
var tasks = new Task<bool>[tasksCounter];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = CreateTask(cts);
}
Console.WriteLine("Waiting..");
Task.WaitAny(tasks);
Console.WriteLine("Done!");
Console.ReadKey();
}
private static Task<bool> CreateTask(CancellationTokenSource cts)
{
return Task.Factory.StartNew(TaskAction, cts.Token).Unwrap();
}
private static async Task<bool> TaskAction()
{
var delay = _rnd.Next(2, 5);
await Task.Delay(delay * 1000);
var taskResult = _rnd.Next(10) < 4;
return await Task.FromResult(taskResult);
}
}
I tried to use Task.WaitAll, Task.WaitAny etc, but none of these methods provide useful (in my case) functionality.
Edit:
As #ckuri stated in the comments, it would be easier to leverage the already existing properties of Task instead of writing a custom result class. Adjusted my answer accordingly.
One solution would be to check for the result in the CreateTask() method and pass a CancellationToken into your TaskAction() method:
private static Task<bool> CreateTask(CancellationTokenSource cts)
{
return Task.Run(async () =>
{
var result = await TaskAction(cts.Token);
// If result is false, cancel all tasks
if (!result)
cts.Cancel();
return result;
});
}
private static async Task<bool> TaskAction(CancellationToken token)
{
// Check for cancellation
token.ThrowIfCancellationRequested();
var delay = Rnd.Next(2, 5);
// Pass the cancellation token to Task.Delay()
await Task.Delay(delay * 1000, token);
var taskResult = Rnd.Next(10) < 4;
// Check for cancellation
token.ThrowIfCancellationRequested();
return taskResult;
}
Now you can do something like this in your Main method to receive all tasks which did not get cancelled:
try
{
// Wait for all tasks inside a try catch block because `WhenAll` throws a `AggregationException`
// containing a `TaskCanceledException`, if the token gets canceled
await Task.WhenAll(tasks);
}
catch { }
var tasksWithResult = tasks.Where(t => !t.IsCanceled).ToList();
I don't have an interpreter on hand, so excuse any mistakes. Your Task needs access to the token if you want to cancel all tasks from the task itself. See https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource?view=netcore-3.0 for an example. I.e. pass it as an argument to the task so one task can cancel the other tasks.
private static async Task<bool> TaskAction(CancellationTokenSource cts)
{
var delay = _rnd.Next(2, 5);
await Task.Delay(delay * 1000);
var taskResult = _rnd.Next(10) < 4;
if (!taskResult)
cts.Cancel()
return await Task.FromResult(taskResult);
}
Just be sure to capture the AggregateException and check if one of the inner excepts is an TaskCanceledException.

Waiting for a single task to fail out of a List<Task<..>> more cleanly, possibly with LINQ?

In my application I have a List<Task<Boolean>> that I Task.Wait[..] on to determine if they completed successfully (Result = true). Though if during my waiting a Task completes and returns a falsey value I want to cancel all other Task I am waiting on and do something based on this.
I have created two "ugly" methods to do this
// Create a CancellationToken and List<Task<..>> to work with
CancellationToken myCToken = new CancellationToken();
List<Task<Boolean>> myTaskList = new List<Task<Boolean>>();
//-- Method 1 --
// Wait for one of the Tasks to complete and get its result
Boolean finishedTaskResult = myTaskList[Task.WaitAny(myTaskList.ToArray(), myCToken)].Result;
// Continue waiting for Tasks to complete until there are none left or one returns false
while (myTaskList.Count > 0 && finishedTaskResult)
{
// Wait for the next Task to complete
finishedTaskResult = myTaskList[Task.WaitAny(myTaskList.ToArray(), myCToken)].Result;
if (!finishedTaskResult) break;
}
// Act on finishTaskResult here
// -- Method 2 --
// Create a label to
WaitForOneCompletion:
int completedTaskIndex = Task.WaitAny(myTaskList.ToArray(), myCToken);
if (myTaskList[completedTaskIndex].Result)
{
myTaskList.RemoveAt(completedTaskIndex);
goto WaitForOneCompletion;
}
else
;// One task has failed to completed, handle appropriately
I was wondering if there was a cleaner way to do this, possibly with LINQ?
Jon Skeet, Stephen Toub, and myself all have variations on the "order by completion" approach.
However, I find that usually people don't need this kind of complexity, if they focus their attention a bit differently.
In this case, you have a collection of tasks, and want them canceled as soon as one of them returns false. Instead of thinking about it from a controller perspective ("how can the calling code do this"), think about it from the task perspective ("how can each task do this").
If you introduce a higher-level asynchronous operation of "do the work and then cancel if necessary", you'll find your calling code cleans up nicely:
public async Task DoWorkAndCancel(Func<CancellationToken, Task<bool>> work,
CancellationTokenSource cts)
{
if (!await work(cts.Token))
cts.Cancel();
}
List<Func<CancellationToken, Task<bool>>> allWork = ...;
var cts = new CancellationTokenSource();
var tasks = allWork.Select(x => DoWorkAndCancel(x, cts));
await Task.WhenAll(tasks);
You can use the following method to take a sequence of tasks and create a new sequence of tasks that represents the initial tasks but returned in the order that they all complete:
public static IEnumerable<Task<T>> Order<T>(this IEnumerable<Task<T>> tasks)
{
var taskList = tasks.ToList();
var taskSources = new BlockingCollection<TaskCompletionSource<T>>();
var taskSourceList = new List<TaskCompletionSource<T>>(taskList.Count);
foreach (var task in taskList)
{
var newSource = new TaskCompletionSource<T>();
taskSources.Add(newSource);
taskSourceList.Add(newSource);
task.ContinueWith(t =>
{
var source = taskSources.Take();
if (t.IsCanceled)
source.TrySetCanceled();
else if (t.IsFaulted)
source.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCompleted)
source.TrySetResult(t.Result);
}, CancellationToken.None, TaskContinuationOptions.PreferFairness, TaskScheduler.Default);
}
return taskSourceList.Select(tcs => tcs.Task);
}
Now that you have the ability to order the tasks based on their completion you can write the code basically exactly as your requirements dictate:
foreach(var task in myTaskList.Order())
if(!await task)
cancellationTokenSource.Cancel();
Using Task.WhenAny implementation, you can create like an extension overload that receives a filter too.
This method returns a Task that will complete when any of the supplied tasks have completed and the result pass the filter.
Something like this:
static class TasksExtensions
{
public static Task<Task<T>> WhenAny<T>(this IList<Task<T>> tasks, Func<T, bool> filter)
{
CompleteOnInvokePromiseFilter<T> action = new CompleteOnInvokePromiseFilter<T>(filter);
bool flag = false;
for (int i = 0; i < tasks.Count; i++)
{
Task<T> completingTask = tasks[i];
if (!flag)
{
if (action.IsCompleted) flag = true;
else if (completingTask.IsCompleted)
{
action.Invoke(completingTask);
flag = true;
}
else completingTask.ContinueWith(t =>
{
action.Invoke(t);
});
}
}
return action.Task;
}
}
class CompleteOnInvokePromiseFilter<T>
{
private int firstTaskAlreadyCompleted;
private TaskCompletionSource<Task<T>> source;
private Func<T, bool> filter;
public CompleteOnInvokePromiseFilter(Func<T, bool> filter)
{
this.filter = filter;
source = new TaskCompletionSource<Task<T>>();
}
public void Invoke(Task<T> completingTask)
{
if (completingTask.Status == TaskStatus.RanToCompletion &&
filter(completingTask.Result) &&
Interlocked.CompareExchange(ref firstTaskAlreadyCompleted, 1, 0) == 0)
{
source.TrySetResult(completingTask);
}
}
public Task<Task<T>> Task { get { return source.Task; } }
public bool IsCompleted { get { return source.Task.IsCompleted; } }
}
You can use this extension method like this:
List<Task<int>> tasks = new List<Task<int>>();
...Initialize Tasks...
var task = await tasks.WhenAny(x => x % 2 == 0);
//In your case would be something like tasks.WhenAny(b => b);

Retry previous task action TPL

I'd like to implement a retry task that takes the previous failing tasks action and repeat it.
This is what I have so far. However it just repeats the fact that the task is in fault rather than actually firing the action of the task again.
public static async Task<T> Retry<T>(this Task<T> task, int retryCount, int delay, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
{
tcs = new TaskCompletionSource<T>();
}
await task.ContinueWith(async _original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
{
tcs.SetException(_original.Exception.InnerExceptions);
}
else
{
Console.WriteLine("Unhandled exception. Retrying...");
await Task.Delay(delay).ContinueWith(async t =>
{
await Retry(task, retryCount - 1, delay, tcs);
});
}
}
else
tcs.SetResult(_original.Result);
});
return await tcs.Task;
}
I tried to get the action with a little reflection. However it seems that once the task is completed the action is set to null.
var action = task
.GetType()
.GetField("m_action", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(task) as Action;
Ideally I would like my implementation to look like this:
try
{
await MakeFailure().Retry(5, 1000);
}
catch (Exception ex)
{
Console.WriteLine("I had an exception");
}
This may not be possible but I'd like to make sure before refactoring the code to a Retry(Func<T> task)
Not completely against it. But it changes the flow of the code to a fault first layout which I don't like
Consider your types. Task represents an asynchronous operation. In the asynchronous world, Task represents an asynchronous operation that has already started. Task is not something you can "retry".
On the other hand, Func<Task> represents an asynchronous operation that can be started. Or restarted. That's what you need to work with.
Once you are using the appropriate type, the code is straightforward:
public static async Task<T> Retry<T>(Func<Task<T>> action, int retryCount, int delay)
{
while (retryCount > 0)
{
try
{
return await action().ConfigureAwait(false);
}
catch (Exception ex)
{
await Task.Delay(delay).ConfigureAwait(false);
--retryCount;
}
}
return await action().ConfigureAwait(false);
}
Like the other answerer, I recommend you use a library that was actually designed for this. The Transient Fault Handling Application Block and Polly are two good examples.
There is a really great library out there that you could leverage without writing your own code. It is called the Transient Fault Application Block. But I would start by evaluating a single library in the block called TransientFaultHandling.Core.
It's used in a way very similar to your code above. Here's a quick example:
using System;
using Microsoft.Practices.TransientFaultHandling;
namespace Stackoverflow
{
class Program
{
internal class MyTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy
{
public bool IsTransient(Exception ex)
{
return true;
}
}
private static void Main(string[] args)
{
const int retryCount = 5;
const int retryIntervalInSeconds = 1;
// define the strategy for retrying
var retryStrategy = new FixedInterval(
retryCount,
TimeSpan.FromSeconds(retryIntervalInSeconds));
// define the policy
var retryPolicy =
new RetryPolicy<MyTransientErrorDetectionStrategy>(retryStrategy);
retryPolicy.Retrying += retryPolicy_Retrying;
for (int i = 0; i < 50; i++)
{
// try this a few times just to illustrate
try
{
retryPolicy.ExecuteAction(SomeMethodThatCanSometimesFail);
// (the retry policy has async support as well)
}
catch (Exception)
{
// if it got to this point, your retries were exhausted
// the original exception is rethrown
throw;
}
}
Console.WriteLine("Press Enter to Exit");
Console.ReadLine();
}
private static void SomeMethodThatCanSometimesFail()
{
var random = new Random().Next(1, 4);
if (random == 2)
{
const string msg = "randomFailure";
Console.WriteLine(msg);
throw new Exception(msg);
}
}
private static void retryPolicy_Retrying(object sender, RetryingEventArgs e)
{
Console.WriteLine("retrying");
}
}
}
The problem that you have is that once you have a Task<T> in flight it can't be undone or retried. You must start with a Func<Task<T>> to be able to retry.
Now you can muck about directly with TPL, but I'd recommend using Microsoft's Reactive Framework to build the functionality you need. It's much much more powerful than TPL.
Given a Func<Task<T>> this is what you need:
Func<Task<T>> taskFactory = ...
int retryCount = 5;
Task<T> retryingTask = Observable.FromAsync(taskFactory).Retry(retryCount).ToTask();
I tested this with this code:
var i = 0;
Func<Task<int>> taskFactory = () => Task.Run(() =>
{
if (i++ == 0)
throw new Exception("Foo");
return i;
});
int retryCount = 5;
Task<int> retryingTask = Observable.FromAsync(taskFactory).Retry(retryCount).ToTask();
Console.WriteLine(retryingTask.Result);
The Reactive Framework will let you do so much more - it's a very powerful library - but it does make this task very simple. You can NuGet "Rx-Main" to get the bits.

Task Parallel Library WaitAny with specified result

I'm trying to write some code that will make a web service call to a number of different servers in parallel, so TPL seems like the obvious choice to use.
Only one of my web service calls will ever return the result I want and all the others won't. I'm trying to work out a way of effectively having a Task.WaitAny but only unblocking when the first Task that matches a condition returns.
I tried with WaitAny but couldn't work out where to put the filter. I got this far:
public void SearchServers()
{
var servers = new[] {"server1", "server2", "server3", "server4"};
var tasks = servers
.Select(s => Task<bool>.Factory.StartNew(server => CallServer((string)server), s))
.ToArray();
Task.WaitAny(tasks); //how do I say "WaitAny where the result is true"?
//Omitted: cancel any outstanding tasks since the correct server has been found
}
private bool CallServer(string server)
{
//... make the call to the server and return the result ...
}
Edit: Quick clarification just in case there's any confusion above. I'm trying to do the following:
For each server, start a Task to check it
Either, wait until a server returns true (only a max of 1 server will ever return true)
Or, wait until all servers have returned false, i.e. there was no match.
The best of what I can think of is specifying a ContinueWith for each Task, checking the result, and if true cancelling the other tasks. For cancelling tasks you may want to use CancellationToken.
var tasks = servers
.Select(s => Task.Run(...)
.ContinueWith(t =>
if (t.Result) {
// cancel other threads
}
)
).ToArray();
UPDATE: An alternative solution would be to WaitAny until the right task completed (but it has some drawbacks, e.g. removing the finished tasks from the list and creating a new array out of the remaining ones is quite a heavy operation):
List<Task<bool>> tasks = servers.Select(s => Task<bool>.Factory.StartNew(server => CallServer((string)server), s)).ToList();
bool result;
do {
int idx = Task.WaitAny(tasks.ToArray());
result = tasks[idx].Result;
tasks.RemoveAt(idx);
} while (!result && tasks.Count > 0);
// cancel other tasks
UPDATE 2: Nowadays I would do it with Rx:
[Fact]
public async Task AwaitFirst()
{
var servers = new[] { "server1", "server2", "server3", "server4" };
var server = await servers
.Select(s => Observable
.FromAsync(ct => CallServer(s, ct))
.Where(p => p)
.Select(_ => s)
)
.Merge()
.FirstAsync();
output.WriteLine($"Got result from {server}");
}
private async Task<bool> CallServer(string server, CancellationToken ct)
{
try
{
if (server == "server1")
{
await Task.Delay(TimeSpan.FromSeconds(1), ct);
output.WriteLine($"{server} finished");
return false;
}
if (server == "server2")
{
await Task.Delay(TimeSpan.FromSeconds(2), ct);
output.WriteLine($"{server} finished");
return false;
}
if (server == "server3")
{
await Task.Delay(TimeSpan.FromSeconds(3), ct);
output.WriteLine($"{server} finished");
return true;
}
if (server == "server4")
{
await Task.Delay(TimeSpan.FromSeconds(4), ct);
output.WriteLine($"{server} finished");
return true;
}
}
catch(OperationCanceledException)
{
output.WriteLine($"{server} Cancelled");
throw;
}
throw new ArgumentOutOfRangeException(nameof(server));
}
The test takes 3.32 seconds on my machine (that means it didn't wait for the 4th server) and I got the following output:
server1 finished
server2 finished
server3 finished
server4 Cancelled
Got result from server3
You can use OrderByCompletion() from the AsyncEx library, which returns the tasks as they complete. Your code could look something like:
var tasks = servers
.Select(s => Task.Factory.StartNew(server => CallServer((string)server), s))
.OrderByCompletion();
foreach (var task in tasks)
{
if (task.Result)
{
Console.WriteLine("found");
break;
}
Console.WriteLine("not found yet");
}
// cancel any outstanding tasks since the correct server has been found
Using Interlocked.CompareExchange will do just that, only one Task will be able to write on serverReturedData
public void SearchServers()
{
ResultClass serverReturnedData = null;
var servers = new[] {"server1", "server2", "server3", "server4"};
var tasks = servers.Select(s => Task<bool>.Factory.StartNew(server =>
{
var result = CallServer((string)server), s);
Interlocked.CompareExchange(ref serverReturnedData, result, null);
}).ToArray();
Task.WaitAny(tasks); //how do I say "WaitAny where the result is true"?
//
// use serverReturnedData as you want.
//
}
EDIT: As Jasd said, the above code can return before the variable serverReturnedData have a valid value (if the server returns a null value, this can happen), to assure that you could wrap the result in a custom object.
Here's a generic solution based on svick's answer:
public static async Task<T> GetFirstResult<T>(
this IEnumerable<Func<CancellationToken, Task<T>>> taskFactories,
Action<Exception> exceptionHandler,
Predicate<T> predicate)
{
T ret = default(T);
var cts = new CancellationTokenSource();
var proxified = taskFactories.Select(tf => tf(cts.Token)).ProxifyByCompletion();
int i;
for (i = 0; i < proxified.Length; i++)
{
try
{
ret = await proxified[i].ConfigureAwait(false);
}
catch (Exception e)
{
exceptionHandler(e);
continue;
}
if (predicate(ret))
{
break;
}
}
if (i == proxified.Length)
{
throw new InvalidOperationException("No task returned the expected value");
}
cts.Cancel(); //we have our value, so we can cancel the rest of the tasks
for (int j = i+1; j < proxified.Length; j++)
{
//observe remaining tasks to prevent process crash
proxified[j].ContinueWith(
t => exceptionHandler(t.Exception), TaskContinuationOptions.OnlyOnFaulted)
.Forget();
}
return ret;
}
Where ProxifyByCompletion is implemented as:
public static Task<T>[] ProxifyByCompletion<T>(this IEnumerable<Task<T>> tasks)
{
var inputTasks = tasks.ToArray();
var buckets = new TaskCompletionSource<T>[inputTasks.Length];
var results = new Task<T>[inputTasks.Length];
for (int i = 0; i < buckets.Length; i++)
{
buckets[i] = new TaskCompletionSource<T>();
results[i] = buckets[i].Task;
}
int nextTaskIndex = -1;
foreach (var inputTask in inputTasks)
{
inputTask.ContinueWith(completed =>
{
var bucket = buckets[Interlocked.Increment(ref nextTaskIndex)];
if (completed.IsFaulted)
{
Trace.Assert(completed.Exception != null);
bucket.TrySetException(completed.Exception.InnerExceptions);
}
else if (completed.IsCanceled)
{
bucket.TrySetCanceled();
}
else
{
bucket.TrySetResult(completed.Result);
}
}, CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
return results;
}
And Forget is an empty method to suppress CS4014:
public static void Forget(this Task task) //suppress CS4014
{
}

Categories

Resources