Exception handling for httpclient.GetStringAsync(url) async api call - c#

if you have the following methods:
public async Task<string> GetTAsync(url)
{
return await httpClient.GetStringAsync(url);
}
public async Task<List<string>> Get(){
var task1 = GetTAsync(url1);
var task2 = GetTAsync(url2);
await Task.WhenAll(new Task[]{task1, task2});
// but this may through if any of the tasks fail.
//process both result
}
How can I handle exception? I looked at the documentation for HttpClient.GetStringAsync(url) method and the only exception it might throw seems to be ArgumentNullException. But at least I run into forbidden error once and would like to handle all possible exception. But i can't find any specific exceptions. Should I catch just Exception exception here? I would appreciate if it is more specific.
Please help, its really important.

Finally i figured it as follows:
public async Task<List<string>> Get()
{
var task1 = GetTAsync(url1);
var task2 = GetTAsync(url2);
var tasks = new List<Task>{task1, task2};
//instead of calling Task.WhenAll and wait until all of them finishes
//and which messes me up when one of them throws, i got the following code
//to process each as they complete and handle their exception (if they throw too)
foreach(var task in tasks)
{
try{
var result = await task; //this may throw so wrapping it in try catch block
//use result here
}
catch(Exception e) // I would appreciate if i get more specific exception but,
// even HttpRequestException as some indicates couldn't seem
// working so i am using more generic exception instead.
{
//deal with it
}
}
}
This is a much better solution i finally figured. If there is something better, i would love to hear about it.
I'm posting this -just case someone else runs into the same issue.

Related

WhenAny behaving like WhenAll in certain case

So I had a problem with a third party library where the call could get stuck and never return even when calling cancellationToken.Cancel. The below is a prototype that take care of this situation and that it works.
public async Task MainAsync()
{
try
{
await StartAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine("Exception thrown");
}
}
private async Task<string> StartAsync()
{
var cts = new CancellationTokenSource();
cts.CancelAfter(3 * 1000);
var tcs = new TaskCompletionSource<string>();
cts.Token.Register(() => { Console.WriteLine("Cancelled"); tcs.TrySetCanceled(); });
return await (await Task.WhenAny(tcs.Task, LongComputationAsync())
.ConfigureAwait(false)).ConfigureAwait(false);
}
private async Task<string> LongComputationAsync()
{
await Task.Delay(1 * 60 * 1000).ConfigureAwait(false);
return "Done";
}
So the Above will wait 3 seconds, and it will throw a TaskCancelledException like it should.
If you then change the method LongComputationAsync to the following:
private Task<string> LongComputationAsync()
{
Task.Delay(1 * 60 * 1000).ConfigureAwait(false).GetAwaiter().GetResult();
return Task.FromResult("Done");
}
I would still expect this to have the same behaviour, but what this does is that, it will wait the full 1 minute (specified in the LongComputationAsync()) then throw the TaskCancelledException.
Can anyone explain this to me? On how this is working, or if this is the correct behaviour to begin with.
Can anyone explain this to me?
Sure. The problem doesn't have anything to do with WhenAny. Rather, the problem is that the code assumes a method is asynchronous when it's synchronous.
This is a relatively easy mistake to make. But as a general rule, a method with an asynchronous signature may be asynchronous; it does not have to be asynchronous.
As I describe on my blog, asynchronous methods begin executing synchronously, just like synchronous methods. It is only when they hit an await that they may run asynchronously (and even then, they may continue synchronously).
So, the new version of LongCompuationAsync is synchronous, and it executes the entire method before returning the task to StartAsync, which then passes it to WhenAny.

Exception from PLinq AsParallel async crashes the app

My question is about capturing exceptions in ForAll method under Plinq
I was trying to run tasks concurently with setting max number of threads.
Using
enumerable
.AsParallel()
.WithDegreeOfParallelism(100)
.ForAll(async item => await AsyncTask())
It works, but if AsyncTask throws exception the app crashes.
I have done the following test:
try
{
IEnumerable<string> enumerable = new List<string> { "st", "st" };
enumerable.AsParallel()
.ForAll(async f =>
{
try
{
throw new Exception(); // Or await AsyncTask that throws this
}
catch (Exception e)
{
e.ToString(); **// This Exception is captured**
throw e;
}
});
}
catch (Exception e) **// THIS IS NOT CAPTURED AND THE APP CRASHES**
{
e.ToString();
}
And I would like to understand the reasons for this
And other options to implement
enumerable.AsParallel().ForAll() executes the given action for each element of your enumeration in parallel. Since your given action is async by itself, ForAll() does not wait until all actions completed. In this case the executed code leaves the try..catch block before your AsyncTask() method throws the exception. This may lead to an unhandled exception, which crashes your app.
It does not matter, that you try to await the AsyncTask(), because ForAll() gets a plain Action and does not await the result of your AsyncTask().
A possible solution could be to start your AsyncTasks for each element without AsParallel().ForEach() and await the results later inside your try..catch.
When storing the
Task or Task<T>
result in a list you can check if any task was throwing an exception using the task.Exception property.
You can do something like this:
private async Task DoSomethingAsync()
{
try
{
IEnumerable<string> enumerable = new List<string> { "st", "st" };
// start all tasks and store them in an array
var tasks = enumerable.Select(TaskAsync).ToArray();
// do something more without waiting until all tasks above completed
// ...
// await all tasks
var completionTask = Task.WhenAll(tasks);
await completionTask;
// handle task exception if any exists
if (completionTask.Status == TaskStatus.Faulted)
{
foreach (var task in tasks)
{
if (task.Exception != null)
{
// throw an exception or handle the exception, e.g. log the exceptions to file / database
}
}
}
}
catch (Exception e)
{
// handle your exception, e.g. write a log to file / database
}
}
private Task TaskAsync(string item)
{
// Task.Delay() is just a placeholder
// do some async stuff here, e.g. access web services or a database
return Task.Delay(10000);
}

Gracefully handeling exceptions when dealing with WhenAll

I'm playing with a piece of code I wrote a while back. That piece of code deals with making a few requests in an async manner.
var client = new HttpClient();
var searchPromises = searchTerms
.Select(GetSearchUrl)
.Select(client.GetStringAsync);
var searchPages = await Task.WhenAll(searchPromises);
What happens is I create a new HttpClient. Using some search terch terms I compose search engine urls. Then I use those urls as inputs to get tasks representing the async requests for a page with the results. And last, I await those responses using Task.WhenAll to group them together.
The problem is if just one of those requests gets a 404, a 500 or anything like that my code throws an AggregateException.
Is there a way of specifying what should happen in the case of an error in one of those threads, so that I get a result from everything else?
I've looked at ContinueWith, but it doesn't seem to fit the bill, that is, it doesn't know how to deal with all the errors, just the aggregate one.
What happens is I create a new HttpClient. Using some search terch terms I compose search engine urls. Then I use those urls as inputs to get tasks representing the async requests for a page with the results. And last, I await those responses using Task.WhenAll to group them together.
Is there a way of specifying what should happen in the case of an error in one of those threads, so that I get a result from everything else?
IMO, the easiest solution is to change how you think about the problem. Right now, you're thinking "perform a download on each url" and then "what for them all to complete and handle errors on a per-item basis". Just change your operation ("download") to include anything you want to do per-item. In other words, what you want to do is "perform a download on each url and handle errors" and then "wait for them all to complete":
var client = new HttpClient();
var searchPromises = searchTerms
.Select(GetSearchUrl)
.Select(url => DownloadAsync(client, url));
var searchPages = await Task.WhenAll(searchPromises);
var successfulSearchPages = searchPages.Where(x => x != null);
...
private static async Task<string> DownloadAsync(HttpClient client, string url)
{
try
{
return await client.GetStringAsync(url);
}
catch (HttpRequestException ex)
{
// TODO: Perform appropriate error handling
return null;
}
}
Task.WhenAll will return a task that is completed when all the tasks passed as argument are completed.
If any of the tasks passed as argument ends in a Faulted state (an exception was thrown), the returned task will also end in a Faulted state and its Exception property will contain the aggregation of all exceptions thrown by the tasks passed as argument.
Because the code generated by the compiler picks the first exceptin on the list, only the excpetion thrown by the first exception that throws (not the first exception thrwing) will be rethrown.
But the tasks passed as argument still exist and can still be queried for result.
This code snippet shows this working:
var tasks = new Task[] {
((Func<Task>)(async () =>
{
await Task.Delay(10);
await Task.Delay(10);
await Task.Delay(10);
throw new Exception("First");
}))(),
((Func<Task>)(async () =>
{
await Task.Delay(10);
throw new Exception("Second");
}))(),
((Func<Task>)(async () =>
{
await Task.Delay(10);
}))()
};
var allTasks = Task.WhenAll(tasks);
try
{
await allTasks;
}
catch (Exception ex)
{
Console.WriteLine("Overall failed: {0}", ex.Message);
}
for(var i = 0; i < tasks.Length; i++)
{
try
{
await tasks[i];
Console.WriteLine("Taks {0} succeeded!", i);
}
catch (Exception ex)
{
Console.WriteLine("Taks {0} failed!", i);
}
}
/*
Overall failed: First
Taks 0 failed!
Taks 1 failed!
Taks 2 succeeded!
*/
You can create your own version of Task.WhenAll that returns just the results disregarding any exception using Task.WhenAny:
public static async Task<IEnumerable<TResult>> WhenAllSwallowExceptions<TResult>(IEnumerable<Task<TResult>> tasks)
{
var tasklist = tasks.ToList();
var results = new List<TResult>();
while (tasklist.Any())
{
var completedTask = await Task.WhenAny(tasklist);
try
{
results.Add(await completedTask);
}
catch (Exception e)
{
// handle
}
tasklist.Remove(completedTask);
}
return results;
}
Usage:
var searchPages = await WhenAllSwallowExceptions(searchPromises);
This waits for tasks one at a time (with Task.WhenAny) and aggregates all the results (if there are any).
I've found a way to do this, after many iterations. Tasks are starting to look like things that you need a library to abstract.
Anyway, here's the code:
var client = new HttpClient();
var exceptions = new ConcurrentBag<Exception>();
var searchPromises = searchTerms
.Select(GetSearchUrl)
.Select(client.GetStringAsync)
.Select(t=>t.Catch(e=>exceptions.Add(e)));
var searchPages = (await Task.WhenAll(searchPromises))
.Where(r => r != null);
And the implementation for Catch:
public static Task<TResult> Catch<TResult>(this Task<TResult> self, Action<Exception> exceptionHandlerTask)
{
return self.ContinueWith(s =>
{
if (!s.IsFaulted)
{
return s.Result;
}
exceptionHandlerTask(s.Exception);
return default(TResult);
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.DenyChildAttach,
TaskScheduler.Default);
}
What happens now is that it gives you a way to append a failure state function to the Task<T> promise. This allows me to still have chainability. It is a shame that c# doesn't have robust support for functional pattern matching to make this easier.
Edit: added minimal code for error logging.
Edit: separated the code for logging errors to be more generic/reusable.
Edit: separated the code for saving the errors from the Catch function.

TaskCanceledException with ContinueWith

I've been trying to figure out why I'm getting a TaskCanceledException for a bit of async code that has recently started misbehaving. I've reduced my issue down to a small code snippet that has me scratching my head:
static void Main(string[] args)
{
RunTest();
}
private static void RunTest()
{
Task.Delay(1000).ContinueWith(t => Console.WriteLine("{0}", t.Exception), TaskContinuationOptions.OnlyOnFaulted).Wait();
}
As far as I'm aware, this should simply pause for a second and then close. The ContinueWith won't be called (this only applies to my actual use-case). However, instead I'm getting a TaskCanceledException and I've no idea where that is coming from!
You are using the wrong taskcontinuationoption:
See following link : https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions%28v=vs.110%29.aspx
It says :
Specifies that the continuation task should be scheduled only if its antecedent threw an unhandled exception. This option is not valid for multi-task continuations.
As guys said above this call requires just antecedent-task in faulted-status otherwise will throw TaskCanceledException, for this concrete case you can generalize ContinueWith to process all statuses:
await Task.Delay(1000).ContinueWith(
task =>
{
/* take into account that Canceled-task throw on next row the TaskCancelledException */
if (!task.IsFaulted) {
return;
}
Console.WriteLine("{0}", task.Exception);
// do smth like 'throw task.Exception.InnerException'
});
I also received this error:
The block of code looked like this:
private void CallMediator<TRequest>(TRequest request) where TRequest : IRequest<Unit>
{
_ = Task.Run(async () =>
{
var mediator = _serviceScopeFactory.CreateScope().ServiceProvider.GetService<IMediator>()!;
await mediator.Send(request).ContinueWith(LogException, TaskContinuationOptions.OnlyOnFaulted);
});
}
private void LogException(Task task)
{
if (task.Exception != null)
{
_logger.LogError(task.Exception, "{ErrorMessage}", task.Exception.Message);
}
}
Reading the documentation for the ContinueWith method, it has the following remarks:
The returned Task will not be scheduled for execution until the current task has completed. If the continuation criteria specified through the continuationOptions parameter are not met, the continuation task will be canceled instead of scheduled.
So for me, it called the first task (mediator.Send(request)), then it continued with the task ContinueWith(...), which is the one I awaited. However, since an exception had not occurred in the first task, the second task was cancelled. Therefore, when awaiting the second task, it threw a TaskCanceledException.
What I did, was to change the code to this:
private void CallMediator<TRequest>(TRequest request) where TRequest : IRequest<Unit>
{
_ = Task.Run(async () =>
{
var mediator = _serviceScopeFactory.CreateScope().ServiceProvider.GetService<IMediator>()!;
try
{
_ = await mediator.Send(request);
}
catch (Exception ex)
{
_logger.LogError(ex, "{ErrorMessage}", ex.Message);
}
});
}
Instead of using .ContinueWith(...), I have replaced it with just a regular try-catch block in case of the task I am interested in fails. I think this simplifies the code and makes it more readable.
In the question, there is this line of code:
Task.Delay(1000).ContinueWith(t => Console.WriteLine("{0}", t.Exception), TaskContinuationOptions.OnlyOnFaulted).Wait();
I would rewrite it to:
try
{
Task.Delay(1000).Wait();
}
catch (Exception ex)
{
Console.WriteLine("{0}", ex);
}

Capturing Exceptions on async operations

I'm reading up more about async here: http://msdn.microsoft.com/en-us/library/hh873173(v=vs.110).aspx
Going through this example:
Task<bool> [] recommendations = …;
while(recommendations.Count > 0)
{
Task<bool> recommendation = await Task.WhenAny(recommendations);
try
{
if (await recommendation) BuyStock(symbol);
break;
}
catch(WebException exc)
{
recommendations.Remove(recommendation);
}
}
I wonder, if I'm already performing await on Task.WhenAny why do I need to await again inside of the try block?
If I already did this: Task<bool> recommendation = await Task.WhenAny(recommendations);
Why do this: if (await recommendation) BuyStock(symbol);
The first await exists to asynchronously wait for the first task to complete (i.e. recommendation).
The second await is only there to extract the actual result out of the already completed task, and throw exceptions stored in the task. (it's important to remember that awaiting a completed task is optimized and will execute synchronously).
A different option to get the result would be using Task<T>.Result, however it differs in the way it handles exceptions. await would throw the actual exception (e.g WebException) while Task<T>.Result would throw an AggregateException containing the actual exception inside.
Task<bool> [] recommendations = …;
while(recommendations.Count > 0)
{
Task<bool> recommendation = await Task.WhenAny(recommendations);
try
{
if (recommendation.Result)
{
BuyStock(symbol);
}
break;
}
catch(AggregateException exc)
{
exc = exc.Flatten();
if (exc.InnerExceptions[0] is WebException)
{
recommendations.Remove(recommendation);
}
else
{
throw;
}
}
}
Clearly awaiting the task is simpler and so it's the recommended way of retrieving a result out of a task.
The use of await here creates the desired error handling semantics. If he used Result instead of await then the AggregateException would be rethrown directly; when using await the first exception within the AggregateException is pulled out an that exception is re-thrown. Clear the author of this code wanted the WebException to be thrown, rather than an AggregateException that he would need to manually unwrap.
Could he have used another approach, sure. This was simply the approach that the author of the code preferred, as it allows him to write the code more like traditional synchronous code rather than radically changing the style of the code.
You're right. It is not necessary. You could replace it with
if (recommendation.Result)
BuyStock(symbol);
Also note that await will not await(will not set continuation) when completed task is given. It will just execute synchronously in that case as a optimization. I guess author leverages that optimization.
If you ask why author wrote that way, May be consistency? only he knows!.
If I already did this: Task recommendation = await Task.WhenAny(recommendations); Why do this: if (await recommendation) BuyStock(symbol);
Because Task.WhenAny returns a Task<Task<bool>> and you want to unwrap the outter Task to retrieve the resulting bool. You could do the same by accessing the Task.Result property of the returned Task
Other answers have pointed out that you have to await the task returned by await Task.WhenAll to unwrap the return value (alternatively, you can use the Result property).
However, you can also get rid of your try/catch (and it's a good thing to avoid catching unnecessary exception)
Task<bool> recommendation = await Task.WhenAny(recommendations);
if(!recommendation.IsFaulted)
{
if (await recommendation) BuyStock(symbol);
break;
}
else
{
if(recommendation.Exception.InnerExceptions[0] is WebException)
{
recommendations.Remove(recommendation);
}
else
{
throw recommendation.Exception.InnerExceptions[0];
}
}
Because Task.WhenAny<TResult>(IEnumerable<Task<TResult>> tasks) returns a Task<Task<TResult>>. The outer task (the one created by the Task.WhenAny call) will complete when any of the tasks passed to it completes with the completed task as a result.

Categories