Run multiple request from Action and wait till one ends - c#

I have web api 2.1 service. Here is my Action :
public IHttpActionResult Get()
{
// Desired functionality :
// make e.g 5 request to `CheckSomething` with different parameter asynchronously/parallel and if any of them returns status Ok end request and return its result as result of `Get` action;
}
public IHttpActionResult CheckSomething(int id)
{
// some code
if(!something)
return NotFound();
return Ok(id);
}
What is best way to achieve this functionality ?

Put your tasks in an array and then call Task.WaitAny:
var finishedTask = Task.WaitAny(myTasks);
When it's finished finishedTask will be the index of the task in myTasks array that finished. You should then be able to get the result from it.
var result = myTasks[finishedTask].Result;
Actually, since you want to wait for the first to return Ok, I'd do something like this:
var taskList = new List<Task>() { ...your tasks ... };
while (taskList.Count > 0)
{
var idx = Task.WaitAny(taskList.ToArray());
if (taskList[idx].Result is Ok) // whatever the appropriate check is?
{
return taskList[idx].Result;
}
taskList.RemoveAt(idx);
}
// If you got here, none of your tasks returned ok
// so handle that however you want

I would use
Task.WhenAll
l to ensure that all task complete before continiuning.
here is an example:
var tasks = new Task[]
{
task1,
task2
};
await Task.WhenAll(tasks);
var task1Result = ((Task<Task1Result>)tasks[0]).Result;
var task2Result = ((Task<Task2Result>)tasks[1]).Result;
if all the task return the same result you don't need to cast, but you can also run task that return different result type in parallel for which you will need to cast back.

Related

Task.WhenAny blocking

I'm not sure if I'm misunderstanding the usage of Task.WhenAny but in the following code only "0" gets printed when it should print "1" and "2" and then "mx.Name" every time a task finishes:
public async void PopulateMxRecords(List<string> nsRecords, int threads)
{
ThreadPool.SetMinThreads(threads, threads);
var resolver = new DnsStubResolver();
var tasks = nsRecords.Select(ns => resolver.ResolveAsync<MxRecord>(ns, RecordType.Mx));
Console.WriteLine("0");
var finished = Task.WhenAny(tasks);
Console.WriteLine("1");
while (mxNsRecords.Count < nsRecords.Count)
{
Console.WriteLine("2");
var task = await finished;
var mxRecords = await task;
foreach(var mx in mxRecords)
Console.WriteLine(mx.Name);
}
}
The DnsStubResolver is part of ARSoft.Tools.Net.Dns. The nsRecords list contains up to 2 million strings.
I'm not sure if I'm misunderstanding the usage of Task.WhenAny
You might be. The pattern you seem to be looking for is interleaving. In the following example, notice the important changes that I have made:
use ToList() to materialize the LINQ query results,
move WhenAny() into the loop,
use Remove(task) as each task completes, and
run the while loop as long as tasks.Count() > 0.
Those are the important changes. The other changes are there to make your listing into a runnable demo of interleaving, the full listing of which is here: https://dotnetfiddle.net/nr1gQ7
public static async Task PopulateMxRecords(List<string> nsRecords)
{
var tasks = nsRecords.Select(ns => ResolveAsync(ns)).ToList();
while (tasks.Count() > 0)
{
var task = await Task.WhenAny(tasks);
tasks.Remove(task);
var mxRecords = await task;
Console.WriteLine(mxRecords);
}
}

Is it possible to return a list of async items in an async method from an async source?

Essentially I'm trying to be able to do this:
var thingTasks = thingFactory.GetMyThings();
// ...
var things = await thingTasks;
I'm trying to start from a list of objects, iterate through that list making an async call for each one, and returning the set of results in an await-able way so the consumer can choose when to await it. GetMyThings itself uses await before generating the list, so it needs to be async itself and is something like:
public async Task<List<Thing>> GetMyThings() {
var thingMakers = await GetThingMakers();
var things = thingMakers.Select(async thing => await thing.GetAsync());
return things;
}
The basic idea is that I have some await lines, then after that I use the results of those lines to generate a list and generating each item also requires an async call. I'm trying to avoid blocking within the method (e.g. .Result) and instead pass that responsibility/opportunity back to the caller. Basically, start the tasks in the list but not await them. This naturally makes me want to return Task<List<Thing>> or 'List>`.
The closest I got was return Task.WhenAll(things) but that didn't work (it needed to be Task<Task<Thing[]>> and await await GetMyThings(). Alternatively, return Select(...) returning a Task<List<Task<Thing>>> and needing a await Task.WhenAll(await GetMyThings()) on the consuming side.
In both cases one needs double await statements to realize the list. I'm thinking it's impossible but is there a way to avoid the double await?
Use Task.WhenAll to await all task at once. This way you will run each GetAsync approximately at the same time. So :
Start all task
Await all
Return task's results
Like this :
public async Task<List<Thing>> GetMyThings()
{
var thingMakers = await GetThingMakers();
var tasks = thingMakers.Select(thing => thing.GetAsync());
var things = await Task.WhenAll(tasks);
return things.ToList();
}
If you want to make the inner tasks await-able outside, you need to actually return them:
public async Task<List<Task<Thing>>> GetMyThings() {
var thingMakers = await GetThingMakers();
var things = thingMakers.Select(thing => thing.GetAsync());
return things.ToList();
}
You can then use this call like this:
List<Task<Thing>> thingTasks = await GetMyThings();
await Task.WhenAll(thingTasks);
List<Thing> = thingTasks.Select(t => t.Result).ToList();
Or even:
List<Thing> things = await GetMyThings()
.ContinueWith(async r =>
{
await Task.WhenAll(r);
return r.Select(r => r.Result).ToList();
});

Async/Await not working as expected

I wanted to call / run three tasks parallely and then wait for their results. From my service class what I did is ...
var _validChapterCodesTask = gd.validateChapterCodeDetails(_input1);
var _validGroupCodesTask = gd.validateGroupCodeDetails(_input1);
await Task.WhenAll(_validChapterCodesTask, _validGroupCodesTask);
var _validChapterCodes = await _validChapterCodesTask;
var _validGroupCodes = await _validGroupCodesTask;
And in my DAL Class , the definitions look like :
public async Task<IEnumerable<ChapterCodeValidationOutput>> validateChapterCodeDetails(GroupMembershipValidationInput gmvi)
{
Repository rep = new Repository();
if (!gmvi._chapterCodes.All(x => x.Equals("")))
{
var _validChapterCodes = await rep.ExecuteSqlQueryAsync(typeof(ChapterCodeValidationOutput),SQL.Upload.UploadValidation.getChapterCodeValidationSQL(gmvi._chapterCodes), null);
return (IEnumerable<ChapterCodeValidationOutput>)_validChapterCodes;
}
else
return new List<ChapterCodeValidationOutput>();
}
public async Task<IEnumerable<GroupCodeValidationOutput>> validateGroupCodeDetails(GroupMembershipValidationInput gmvi)
{
Repository rep = new Repository();
if (!gmvi._chapterCodes.All(x => x.Equals("")))
{
var _validGroupCodes = await rep.ExecuteSqlQueryAsync(typeof(GroupCodeValidationOutput), SQL.Upload.UploadValidation.getGroupCodeValidationSQL(gmvi._groupCodes), null);
return (IEnumerable<GroupCodeValidationOutput>)_validGroupCodes;
}
else
return new List<GroupCodeValidationOutput>();
}
But these are not working as expected ? The breakpoints hit show that one by one the method is getting called from service, going to DAL, execute the DB Query and comeback and assign to respective variables.
And as soon as it hits
await Task.WhenAll(_validChapterCodesTask, _validGroupCodesTask);
The next few lines are not hit and the control returns to the invoking controller.
How would I get back their results and fire them parallely and wait for their results only?
What am I doing wrong ?
await Task.WhenAll(_validChapterCodesTask, _validGroupCodesTask)
creates a Task that should be waited for. Use await in the invoking controller.

Access results in Enumerable.Range without having to wait for all tasks to complete

I have a console app, where I need to access some url 200 times, wait for all of the requests to return and work on the 200 results.
I did it like that, in parallel:
var classNameTasks = Enumerable.Range(1, 200).Select(i => webApi.getSplittedClassName()).ToArray();
string[][] splittedClassNames = await Task.WhenAll(classNameTasks);
if (splittedClassNames[0] == null)
result = new TextResult("Error accessing the web");
getSplittedClassName returns a string[], if the internet is off it will return null.
Now, as you can see, after the completion of all the tasks, I do an if to check the content, if its null -> internet issues.
The problem here is that I need to wait for the whole 200 requests to return before I can check the content.
I am looking for a way to right away detect a scenario where there is no internet, and I return null, without having to wait for the 200 requests.
To do this you need
A CancellationTokenSource to signal that the job is done.
The WhenAll method from Tortuga.Anchor
static async Task Test()
{
TextResult result;
var cts = new CancellationTokenSource();
var classNameTasks = Enumerable.Range(1, 200).Select(i => getSplittedClassName(cts)).ToArray();
await classNameTasks.WhenAll(cts.Token);
if (cts.IsCancellationRequested)
result = new TextResult("Error accessing the web");
string[][] splittedClassNames = classNameTasks.Select(t => t.Result).ToArray();
}
private static async Task<string[]> getSplittedClassName(CancellationTokenSource cts)
{
try
{
//real code goes here
await Task.Delay(1000, cts.Token); //the token would be passed to the real web method
return new string[0];
}
catch
{
cts.Cancel(); //stop trying
return null;
}
}

How to cancel other threads as soon as one completed thread satisfies condition

I have an ASP.NET MVC application which needs to check if something exists at 3 remote API servers. The application passes an ID to each API and it returns either true or false. The code looks like this.
public class PingController
{
public async Task<bool> IsFound(int id)
{
var servers = new ['a.com', b.com', 'c.com'];
var result = await foundAtServers(id, servers);
return result;
}
private async Task<bool> foundAtServers(int id, string[] servers)
{
var tasks = from server in servers
select checkServer(id, server);
return await.Task.WhenAll(tasks.ToArray());
}
private async Task<bool> checkServer(id, server)
{
var request = new HttpRequestMessage(HttpMethod.Get, server+"/api/exists"+id);
var client = new HttpClient();
var task = await client.SendAsync(request);
var response = await task.Content.ReadAsStringAsync();
return bool.Parse(response);
}
}
This code currently checks all 3 APIs asynchronously but will wait until ALL of the HttpClient calls have completed before the MVC Action can return.
As soon as one API returns true I want to immediately return true on the Action, rather than wait for the other tasks to complete.
The C# Task class has .WaitAll and .WaitAny, but these won't work either. As I need to cancel the other HttpClient request, I presume I need to use a CancellationToken but I don't know how to use it with this structure.
Cheers.
If you want to immediately return, you can use Task.WhenAny instead of Task.WhenAll. This won't cancel the on-going tasks, but it will enable you to return as soon as possible:
private async Task<bool> FoundAtServersAsync(int id, string[] servers)
{
var tasks = (from server in servers
select checkServer(id, server)).ToList();
while (tasks.Count > 0)
{
var finishedTask = await Task.WhenAny(tasks);
if (finishedTask.Result)
{
return finishedTask.Result;
}
tasks.Remove(finishedTask);
}
return false;
}
This will discard the other tasks. This means that if any exception is thrown inside one of them, it will be swallowed.
Edit:
If you care about actually canceling the other tasks, consider passing your CancellationToken to the overload of SendAsync which takes one, and calling CancellationTokenSource.Cancel once a value is received. Note this will mean you'll also need to handle the OperationCanceledException they will throw.
If they don't matter, i'd simply discard them as above.
This problem is made easier by using the following method to take a sequence of tasks and order them based on when they are completed.
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);
}
With this you can write:
public static async Task<bool> WhenAny(this IEnumerable<Task<bool>> tasks)
{
foreach (var task in tasks.Order())
if (await task)
return true;
return false;
}
You could wait for the first task to complete - if it's successful, return true immediately. Otherwise, wait for the next one to complete, and so on and so forth.
private async Task<bool> foundAtServers(int id, string[] servers)
{
var tasks = servers.Select(server => checkServer(id, server))
.ToList();
while(tasks.Any())
{
var task = await Task.WhenAny(tasks);
if(task.Result)
return true;
tasks.Remove(task);
}
return false;
}

Categories