Reputation: 727
I'm using async/await to call few external APIs. All of them returns me a string value but in different format and requires their own processing. And I want to process the returned value as a task completes. I don't want to wait until all are completed and hence I'm using Task.WhenAny(). How can I process tasks as they complete and still use the correct "Process" method for each task as they complete?
I make some changes after my first post and here is the latest i have:
public async Task<List<string>> Get()
{
var task1 = Method1Async();
var task2 = Method1Async();
var tasks = new List<Task<string>> {task1, task2};
var results = new List<string>();
while (tasks.Count > 0)
{
var justCompletedTask = await Task.WhenAny(tasks);//will not throw
tasks.Remove(justCompletedTask);
try
{
var result = await justCompletedTask;
results.Add(result);
}
catch(Exception)
{
//deal with it
}
}
return results;
}
private async Task<string> Method1Async()
{
//this may throw - something like forbidden or any other exception
var task = _httpClient.GetStringAsync("api1's url here");
var result = await Method1ResultProcessorAsync(task);
return result;
}
private async Task<string> Method1ResultProcessorAsync(Task<string> task)
{
//process task's result -if it successuflly completed and return that
return await task; //for now
}
private async Task<string> Method2Async()
{
//this may throw - something like forbidden or any other exception
var task = _httpClient.GetStringAsync("api2's url here");
var result = await Method2ResultProcessorAsync(task);
return await task;
}
private async Task<string> Method2ResultProcessorAsync(Task<string> task)
{
//This processing logic is entirely different from Method1ResultProcessor
//process task's result -if it successfully completed and return that
return await task; //for now
}
I have two questions here:
Upvotes: 3
Views: 2205
Reputation: 244797
Since your processor methods already accept Task
, you can just call them and they will asynchronously wait for their corresponding results:
public Task<string[]> Get()
{
var task1 = Method1ResultProcessorAsync(Method1Async());
var task2 = Method2ResultProcessorAsync(Method2Async());
return Task.WhenAll(task1, task2);
}
Handling exceptions the way you describe will make this more complicated, but you can use something like:
public async Task<List<string>> Get()
{
var task1 = Method1ResultProcessorAsync(Method1Async());
var task2 = Method2ResultProcessorAsync(Method2Async());
var tasks = new[] { task1, task2 };
try
{
await Task.WhenAll(tasks);
}
catch {}
var results = tasks.Where(t => t.Status == TaskStatus.RanToCompletion)
.Select(t => t.Result)
.ToList();
if (results.Any())
return results;
// or maybe another exception,
// since await handles AggregateException in a weird way
throw new AggregateException(tasks.Select(t => t.Exception));
}
Upvotes: 1
Reputation: 5357
Here is an alternative way of describing Method1Async()
and Method2Async()
. This is the demonstration of ContinueWith
. Answering the question you ask in title – you do can use a different method for each task, that method will be called after the task completes.
var task1 = _httpClient.GetStringAsync("api1's url here").ContinueWith(t => Method1ResultProcessorAsync(t));
var task2 = _httpClient.GetStringAsync("api2's url here").ContinueWith(t => Method2ResultProcessorAsync(t));
You handle the exceptions correctly. Answering "But if all fails, I want to the Get method to throw.": just check whether results.Count == 0
before the return
, and throw
if it 0.
Upvotes: 0