Gracefully handeling exceptions when dealing with WhenAll - c#

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.

Related

Task.WhenAll on List<Task> behaving differently than Task.WhenAll on IEnumerable<Task>

I'm seeing some odd behavioral differences when calling Task.WhenAll(IEnumerable<Task<T>>) and calling Task.WhenAll(List<Task<T>>) while trying to catch exceptions
My code is as follows:
public async Task Run()
{
var en = GetResources(new []{"a","b","c","d"});
await foreach (var item in en)
{
var res = item.Select(x => x.Id).ToArray();
System.Console.WriteLine(string.Join("-> ", res));
}
}
private async IAsyncEnumerable<IEnumerable<ResponseObj>> GetResources(
IEnumerable<string> identifiers)
{
IEnumerable<IEnumerable<string>> groupedIds = identifiers.Batch(2);
// MoreLinq extension method -- batches IEnumerable<T>
// into IEnumerable<IEnumerable<T>>
foreach (var batch in groupedIds)
{
//GetHttpResource is simply a wrapper around HttpClient which
//makes an Http request to an API endpoint with the given parameter
var tasks = batch.Select(id => ac.GetHttpResourceAsync(id)).ToList();
// if I remove this ToList(), the behavior changes
var stats = tasks.Select(t => t.Status);
// at this point the status being WaitingForActivation is reasonable
// since I have not awaited yet
IEnumerable<ResponseObj> res = null;
var taskGroup = Task.WhenAll(tasks);
try
{
res = await taskGroup;
var awaitedStats = tasks.Select(t => t.Status);
//this is the part that changes
//if I have .ToList(), the statuses are RanToCompletion or Faulted
//if I don't have .ToList(), the statuses are always WaitingForActivation
}
catch (Exception ex)
{
var exceptions = taskGroup.Exception.InnerException;
DoSomethingWithExceptions(exceptions);
res = tasks.Where(g => !g.IsFaulted).Select(t => t.Result);
//throws an exception because all tasks are WaitingForActivation
}
yield return res;
}
}
Ultimately, I have an IEnumerable of identifiers, I'm batching that into batches of 2 (hard coded in this example), and then running Task.WhenAll to run each batch of 2 at the same time.
What I want is if 1 of the 2 GetResource tasks fails, to still return the successful result of the other, and handle the exception (say, write it to a log).
If I run Task.WhenAll on a list of tasks, this works exactly how I want. However, if I remove the .ToList(), when I attempt to find my faulted tasks in the catch block after the await taskGroup, I run into problems because the statuses of my tasks are still WaitingForActivation although I believe they have been awaited.
When there is no exception thrown, the List and IEnumerable act the same way. This only starts causing issues when I try to catch exceptions.
What is the reasoning behind this behavior? The Task.WhenAll must have completed since I get into the catch block, however why are the statuses still WaitingForActivation? Have I failed to grasp something fundamental here?
Unless you make the list concrete (by using ToList()), each time you enumerate over the list you are calling GetHttpResourceAsync again, and creating a new task. This is due to the deferred execution.
I would definitely keep the ToList() call when working with a list of tasks

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);
}

await Task.WhenAll(tasks) Exception Handling, log all exceptions from the tasks

I am trying to figure out how to report all exceptions thrown by a list of tasks from the code below.
The basic idea of this code snippet is: The user sends a request to the handler, the handler creates the messages tasks and send them to a class that send them to the external system. I included the methods involved below.
I must have something off because I was debugging the exception handler and the tasks Exception are always null because it seems their status is Waiting for Activiation unless I stay in the breakpoint long enough.
// Handle the user request
public async void Handle(WriteScanToSys settings)
{
_successfulScanIds = new List<int>();
// create the messages as tasks
var tasks = _factory.CreateMessage(settings).Select(msg => SendScans(msg));
try
{
// wait for all of them to complete
await Task.WhenAll(tasks); // I had ConfigureAwait(false) here, but took it off
}
catch (Exception)
{
foreach (var task in tasks.Where(t => t.Exception != null))
{
// ELMAH
var errorLog = ErrorLog.GetDefault(null);
errorLog.Log(new Error(task.Exception));
}
}
// save to repository
}
// the task to perform
private async Task<IDictionary<string, object>> SendScans(IDictionary<string, object> data)
{
object sysMsg = null;
var response = await _msgCenter.SendMessage(data);
response.TryGetValue("SystemMessage", out sysMsg);
_successfulScanIds.Add(Convert.ToInt32(data["Id"]));
return response;
}
// the communication with the external system (The message center class)
private async Task<IDictionary<string, object>> SendMessage(IDictionary<string, object> msg)
{
var response = new Dictionary<string, object>();
var response = await _client.sendAsync(
new BodyOfRequest(
// Compose Object
));
if (response.ScreenMessage != "SUCCESSFUL")
throw new CustomException("The transaction for job " + job + " failed with msg: " + body.ScreenMessage);
response.Add("SystemMessage", body.ScreenMessage);
return response;
}
You've fallen foul of lazy evaluation - the result of Select will create a new set of tasks each time you iterate over it. You can fix this just by calling ToList():
var tasks = _factory.CreateMessage(settings)
.Select(msg => SendScans(msg))
.ToList();
That way the set of tasks that you're awaiting will be the same set of tasks checked with your foreach loop.
Instead of iterating over all tasks, you can get the Exceptions (if any) from the Task.WhenAll-Task:
var taskResult = Task.WhenAll(tasks);
try
{
await taskResult;
}
catch (Exception e)
{
if (taskResult.IsCanceled)
{
// Cancellation is most likely due to a shared cancellation token. Handle as needed, possibly check if ((TaskCanceledException)e).CancellationToken == token etc.
}
else if (taskResult.IsFaulted)
{
// use taskResult.Exception which is an AggregateException - which you can iterate over (it's a tree! .Flatten() might help)
// caught exception is only the first observed exception
}
else
{
// Well, this should not really happen because it would mean: Exception thrown, not faulted nor cancelled but completed
}
}

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

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.

Good pattern for exception handling when using async calls

I want to consume an Web API and I see many people recommending System.Net.Http.HttpClient.
That's fine... but I have only VS-2010, so I cannot use async/await just yet. Instead, I guess I could use Task<TResult> in combination to ContinueWith. So I tried this piece of code:
var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
client.GetStringAsync(STR_URL_SERVER_API_USERS).ContinueWith(task =>
{
var usersResultString = task.Result;
lbUsers.DataSource = JsonConvert.DeserializeObject<List<string>>(usersResultString);
});
My first observation was to realize that it doesn't generate any error if URL is not available, but maybe there will be more errors like this...
So I am trying to find a way to handle exceptions for such async calls (particularly for HttpClient). I noticed that "Task" has IsFaulted property and an AggregateException which maybe could be used, but I am not sure yet how.
Another observation was that GetStringAsync returns Task<string>, but GetAsync returns Task<HttpResponseMessage>. The latter could be maybe more useful, since it presents a StatusCode.
Could you share a pattern on how to use the async calls correctly and handle exceptions in a good way? Basic explanation would be appreciated as well.
I would not use a separate ContinueWith continuation for successful and faulted scenarios. I'd rather handle both cases in a single place, using try/catch:
task.ContinueWith(t =>
{
try
{
// this would re-throw an exception from task, if any
var result = t.Result;
// process result
lbUsers.DataSource = JsonConvert.DeserializeObject<List<string>>(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
lbUsers.Clear();
lbUsers.Items.Add("Error loading users!");
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()
);
If t is a non-generic Task (rather than a Task<TResult>), you can do t.GetAwaiter().GetResult() to re-throw the original exception inside the ContinueWith lambda; t.Wait() would work too. Be prepared to handle AggregatedException, you can get to the inner exception with something like this:
catch (Exception ex)
{
while (ex is AggregatedException && ex.InnerException != null)
ex = ex.InnerException;
MessageBox.Show(ex.Message);
}
If you're dealing with a series of ContinueWith, usually you don't have to handle exceptions inside each ContinueWith. Do it once for the outermost resulting task, e.g.:
void GetThreePagesV1()
{
var httpClient = new HttpClient();
var finalTask = httpClient.GetStringAsync("http://example.com")
.ContinueWith((task1) =>
{
var page1 = task1.Result;
return httpClient.GetStringAsync("http://example.net")
.ContinueWith((task2) =>
{
var page2 = task2.Result;
return httpClient.GetStringAsync("http://example.org")
.ContinueWith((task3) =>
{
var page3 = task3.Result;
return page1 + page2 + page3;
}, TaskContinuationOptions.ExecuteSynchronously);
}, TaskContinuationOptions.ExecuteSynchronously).Unwrap();
}, TaskContinuationOptions.ExecuteSynchronously).Unwrap()
.ContinueWith((resultTask) =>
{
httpClient.Dispose();
string result = resultTask.Result;
try
{
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
Any exceptions thrown inside inner tasks will propagate to the outermost ContinueWith lambda as you're accessing the results of the inner tasks (taskN.Result).
This code is functional, but it's also ugly and non-readable. JavaScript developers call it The Callback Pyramid of Doom. They have Promises to deal with it. C# developers have async/await, which you're unfortunately not able to use because of the VS2010 restrain.
IMO, the closest thing to the JavaScript Promises in TPL is Stephen Toub's Then pattern. And the closest thing to async/await in C# 4.0 is his Iterate pattern from the same blog post, which uses the C# yield feature.
Using the Iterate pattern, the above code could be rewritten in a more readable way. Note that inside GetThreePagesHelper you can use all the familiar synchronous code statements like using, for, while, try/catch etc. It is however important to understand the asynchronous code flow of this pattern:
void GetThreePagesV2()
{
Iterate(GetThreePagesHelper()).ContinueWith((iteratorTask) =>
{
try
{
var lastTask = (Task<string>)iteratorTask.Result;
var result = lastTask.Result;
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
IEnumerable<Task> GetThreePagesHelper()
{
// now you can use "foreach", "using" etc
using (var httpClient = new HttpClient())
{
var task1 = httpClient.GetStringAsync("http://example.com");
yield return task1;
var page1 = task1.Result;
var task2 = httpClient.GetStringAsync("http://example.net");
yield return task2;
var page2 = task2.Result;
var task3 = httpClient.GetStringAsync("http://example.org");
yield return task3;
var page3 = task3.Result;
yield return Task.Delay(1000);
var resultTcs = new TaskCompletionSource<string>();
resultTcs.SetResult(page1 + page1 + page3);
yield return resultTcs.Task;
}
}
/// <summary>
/// A slightly modified version of Iterate from
/// http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx
/// </summary>
public static Task<Task> Iterate(IEnumerable<Task> asyncIterator)
{
if (asyncIterator == null)
throw new ArgumentNullException("asyncIterator");
var enumerator = asyncIterator.GetEnumerator();
if (enumerator == null)
throw new InvalidOperationException("asyncIterator.GetEnumerator");
var tcs = new TaskCompletionSource<Task>();
Action<Task> nextStep = null;
nextStep = (previousTask) =>
{
if (previousTask != null && previousTask.Exception != null)
tcs.SetException(previousTask.Exception);
if (enumerator.MoveNext())
{
enumerator.Current.ContinueWith(nextStep,
TaskContinuationOptions.ExecuteSynchronously);
}
else
{
tcs.SetResult(previousTask);
}
};
nextStep(null);
return tcs.Task;
}

Categories