Handling Aggregate Exceptions in Service Fabric - c#

Let's say I have a Web API service that calls my user Service to return user profile information etc..
UserProfileService can throw UserNotFoundException. When thrown, it is serialized and sent as an inner exception in an AggregateException which can be caught in the calling method. This service uses Service Fabric's service remoting for RPCing.
My WebAPI is calling my service like this:
[HttpGet]
public async Task<IActionResult> Get(int id)
{
try
{
var profile = await _userService.GetProfileAsync(int id);
return Json(profile);
} catch (AggregateException ae)
{
// Here I want to call NotFound() if `UserNotFoundException`
// was thrown, otherwise...
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
So a couple of questions here:
What do I do to handle expected exceptions?
Naively I'd do something like this:
try { /* ... */ } catch (AggregateException ae)
{
foreach(var e in ae.InnerExceptions)
{
if (e is UserNotFoundException)
{
return NotFound();
}
}
return errorResponse ?? StatusCode(StatusCodes.Status500InternalServerError);
}
But the trouble with this is, if there are multiple exceptions only one will "win". And, I believe - although there is no guarantee, that the earliest added Exceptions will have priority, as they'll have a lower index in InnerExceptions. Am I over thinking this, and would this solution be fine? The only time my custom exceptions will be thrown are when I know they should be thrown, surely?
This leads me to my other question:
Under what circumstances would you retrieve several exceptions in an AggregateException.
Is it when you have Task a calling Task b calling a Task c, c throws, b doesn't throw, a throws, you'd get aggregate exception containing a and c's exceptions?

I'll answer your questions backwards:
2) AggregateException has a contructor that allows an IEnumerable<Exception> as parameter. This is how it can contain multiple inner exceptions. This means that your aggregate exception won't contain more than one inner exception unless you explicitly throw an AggregateException with multiple inner exceptions. Say you have a Task a calling Task b calling Task c. If c throws an exception, which is not caught in a or b, a would throw an AggregateException with an inner AggregateException with an inner exception thrown by c.
1) Your example works just fine. If you want it a bit shorter you could catch it by inner exception:
try
{
// ...
}
catch (AggregateException ex) when (ex.InnerException is UserNotFoundException)
{
// ...
}
catch (AggregateException ex) when (ex.InnerException is SomeOtherException)
{
// ...
}
You could also catch them with some if-statements like you did in your example:
try
{
// ...
}
catch (AggregateException ex)
{
if (ex.InnerException is UserNotFoundException)
{
// ...
}
else if (ex.InnerException is SomeOtherExeption)
{
// ...
}
}

I would recommend to throw exceptions in all relevant classes, and to only catch those at the web service, that reports one error message to the web service caller. Keep the exception handling as simple as possible.
To rephrase: if something goes wrong in whatever code, just throw new Exception() with a string indicating what happened and where. The only place where you do try/catch is in the [httpget] Get().
If you really know what you are doing, you could implement serialized exceptions stuff, but then you would not ask about that here :-)

Related

Is there a way to handle exceptions thrown by a task without the task freezing the UI?

public async void CallTask()
{
try
{
await Task.Run(MyTaskMethod);
}
catch (ArgumentException ex) // Exception doesn't get handled
{
MessageBox.Show(ex.Message);
}
}
public Task MyTaskMethod()
{
throw new ArgumentException("This is an error message");
}
My task throws an exception I want to capture in a higher level.
How can I handle the exception being thrown on MyTaskMethod without freezing the UI ?
Two options:
Catch the exception in MyTaskMethod
Catch AggregateException that is thrown by the Task
I believe 1 is fairly straight forwards to understand.
Number 2 looks like this:
public async void CallTask()
{
try
{
await Task.Run(MyTaskMethod);
}
catch (AggregateException ex) // Exception doesn't get handled
{
MessageBox.Show(ex.InnerExceptions[0].Message);
}
}
public Task MyTaskMethod()
{
throw new ArgumentException("This is an error message");
}
This is necessary because when an exception is thrown on a Task it gets wrapped in an AggregateException before being returned. This means trying to catch the inner exception will fail, so we need to first catch the AggregateException and then unwrap.
You can't if you use a message box: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.messagebox?view=net-5.0
Displays a message window, also known as a dialog box, which presents a message to the user. It is a modal window, blocking other actions in the application until the user closes it.
You can use inline labels in your form and set the text property and show then only on error.
If your problem is that your exception is not handled, then catch the AggregateException https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library
To propagate all the exceptions back to the calling thread, the Task infrastructure wraps them in an AggregateException instance. The AggregateException exception has an InnerExceptions property that can be enumerated to examine all the original exceptions that were thrown
public async void CallTask()
{
try
{
await Task.Run(MyTaskMethod);
}
catch (AggregateException ex) // Exception does get handled
{
// access inner exceptions here.
}
}

Handling multiple exceptions from async parallel tasks

Problem
Several tasks are run in parallel, and all, none, or any of them might throw exceptions. When all the tasks have finalized, all the exceptions that might have happened must be reported (via log, email, console output.... whatever).
Expected behavior
I can build all the tasks via linq with async lambdas, and then await for them running in parallel with Task.WhenAll(tasks). Then I can catch an AggregateException and report each of the individual inner exceptions.
Actual behavior
An AggregateException is thrown, but it contains just one inner exception, whatever number of individual exceptions have been thrown.
Minimal complete verifiable example
static void Main(string[] args)
{
try
{
ThrowSeveralExceptionsAsync(5).Wait();
}
catch (AggregateException ex)
{
ex.Handle(innerEx =>
{
Console.WriteLine($"\"{innerEx.Message}\" was thrown");
return true;
});
}
Console.ReadLine();
}
private static async Task ThrowSeveralExceptionsAsync(int nExceptions)
{
var tasks = Enumerable.Range(0, nExceptions)
.Select(async n =>
{
await ThrowAsync(new Exception($"Exception #{n}"));
});
await Task.WhenAll(tasks);
}
private static async Task ThrowAsync(Exception ex)
{
await Task.Run(() => {
Console.WriteLine($"I am going to throw \"{ex.Message}\"");
throw ex;
});
}
Output
Note that the output order of the "I am going to throw" messages might change, due to race conditions.
I am going to throw "Exception #0"
I am going to throw "Exception #1"
I am going to throw "Exception #2"
I am going to throw "Exception #3"
I am going to throw "Exception #4"
"Exception #0" was thrown
That's because await "unwraps" aggregate exceptions and always throws just first exception (as described in documentation of await), even when you await Task.WhenAll which obviously can result in multiple errors. You can access aggregate exception for example like this:
var whenAll = Task.WhenAll(tasks);
try {
await whenAll;
}
catch {
// this is `AggregateException`
throw whenAll.Exception;
}
Or you can just loop over tasks and check status and exception of each.
Note that after that fix you need to do one more thing:
try {
ThrowSeveralExceptionsAsync(5).Wait();
}
catch (AggregateException ex) {
// flatten, unwrapping all inner aggregate exceptions
ex.Flatten().Handle(innerEx => {
Console.WriteLine($"\"{innerEx.Message}\" was thrown");
return true;
});
}
Because task returned by ThrowSeveralExceptionsAsync contains AggregateException we thrown, wrapped inside another AggregateException.

Debugging exception through multiple nested async calls

Per the answer to this question, the form for capturing an exception thrown by an asynchronous method looks like this:
public async void DoFoo()
{
try
{
await Foo();
}
catch (ProtocolException ex)
{
/* The exception will be caught because you've awaited the call. */
}
}
Great. This seems to disintegrate if I want to bubble up several levels of asynchrony though. Here's where the exception originates:
internal static async Task MakePdfPagesFromPdf(Pdf pdf, byte[] pdfBytes, int jobId)
{
IEnumerable<Image> pdfAsImages = PdfOperations.PdfToImagesPdfium(pdfBytes, dpi);
if(pdfAsImages.Count() < 1)
{
throw new ArgumentException("PDF has no pages.");
}
// ... more code ...
}
Here's the method that calls MakePdfPagesFromPdf:
internal static async Task ProcessBase64Pdf(Pdf pdf, int jobId, string componentDesignName)
{
byte[] pdfBytes = ConvertBase64ToPdfByteArray(pdf.Url); // Base64 data is in pdf.Url
await UploadPdfToAwsS3(pdf, pdfBytes, jobId, componentDesignName);
try
{
await MakePdfPagesFromPdf(pdf, pdfBytes, jobId);
}
catch(ArgumentException argumentException)
{
throw argumentException;
}
}
I catch the exception like in the example cited at the beginning of this question. Debugging asserts that this catch block is hit. However, I need to bubble the exception up one more level, to inside a controller route:
try
{
await PdfsController.ProcessBase64Pdf(pdf, componentDesign.JobId, componentDesign.Name);
}
catch (ArgumentException argumentException)
{
// Now do stuff with the exception
}
It doesn't hit this highest level catch at a breakpoint. Removing the intermediate catch has no effect. The route continues and returns, but I am not able to hit breakpoints after the ArgumentException is thrown from the intermediate catch. What's going on here and how can I hit breakpoints through this whole asynchronous stack?
If the method that you want to propogate the exception is async void (such as in your example of DoFoo), then the issue is that there is no Task object to propagate the exception with (since the method is void and does not return a Task)
Another thing I suggest is to not throw argumentException, but rather just throw, as the former loses the call stack of the original exception
I'm guessing Argument exception is part of an inner exception. And is not the thrown exception. You should change catch (ArgumentException argumentException) to catch (ArgumentException exception) to call "all" exceptions.

Problems with handling exceptions from async method with tasks

I hope you can help me with that problem.
I have a method that does specific actions, for example, I am sending a HttpWebRequest. There I can get a WebException, so I put it in a Try-Catch-block to rethrow exceptions for more specific exception messages.
Like that: (This is in a method called doWebRequest)
try
{
// HttpWebRequest here
}
catch (WebException ex)
{
throw new WebException(String.Format("My special additional message {0}", ex.Message);
}
Well, so this works, when I call this function normally.
But now I want an async way to do this. What I made to call this method in an async method:
public void DoRequestAsync()
{
Task internalRequest = new Task(doWebRequest);
internalRequest.ContinueWith(InternalUpdateSearchExceptionHandler, TaskContinuationOptions.OnlyOnFaulted);
internalRequest.Start();
}
So this makes a new task and then calls the method doWebRequest async.
To handle errors now, because I know it is different on async, I made this handler, which you can also see in internalRequest.ContinueWith. Looks like that:
private void InternalUpdateSearchExceptionHandler(Task task)
{
var ex = task.Exception;
if (ex.InnerException is WebException)
{
if ((ex.InnerException as WebException).Status == WebExceptionStatus.ProtocolError)
{
throw new WebException(ex.InnerException.Message);
}
else
{
throw new Exception("There was no response from the server.");
}
}
}
But this is not executing any exceptions. I don't know why.
At first I thought, this is because it cannot take the InnerException as a WebException, or would that work? If not, please tell me what to do here. But even when I throw an exception without any queries here, it did not throw any exceptions while debugging. Why is that?
Help is appreciated. When something is not clear, ask me. ;)
Your continuation is throwing an exception, but when that continuation throws an exception all that happens is the Task that represents that continuation is marked as Faulted with the given exception as its Exception. You currently are ignoring the task created by calling ContinueWith, so there is nothing to observe this exception.
DoRequestAsync likely shouldn't be void; rather it should return a Task, specifically the one created by calling ContinueWith, so that whoever calls this method can observe any exceptions thrown.
Also note that you could do this much more simply using async:
public async Task DoRequestAsync()
{
try
{
var content = await new WebClient()
.DownloadStringTaskAsync("address");
}
catch (WebException ex)
{
throw new WebException(String.Format("My special additional message {0}", ex.Message);
}
}

Proper exception handling when invoking WCF callback method

I have a series of WCF services that invoke methods on the client again (using WcfDuplexChannels) based on events at the server side. However, it seems there are quite some exceptions that can occur, so right now I have a huge try/catch block around every line calling back to the client, ending with disabling of the event in case any exception occurs. Besides being cumbersome to write every time, I'm not sure if I could simplify the try catch block by catching just a few base exceptions? Right now I don't really care what's causing the exceptions (I don't care whether it's faulted, aborted, disposed or timed out) but I do log the different exceptions.
I also read about IErrorHandler, but will that actually be suitable when invoking a method on the client?
Here's a sample of my current strategy:
private void OnProductChanged(List<DTO> products)
{
try
{
client.OnProductChanged(products);
return;
}
catch (TimeoutException)
{
log.Info("Communication to client timed out.");
}
catch (CommunicationObjectAbortedException)
{
log.Info("Connection to client is in aborted state.");
}
catch (CommunicationObjectFaultedException)
{
log.Info("Connection to client is in faulted state.");
}
catch (CommunicationException ce)
{
log.InfoFormat("CommunicationException occured on product change notification: {0}.", ce.Message);
}
catch (ObjectDisposedException)
{
log.Info("Communication channel is disposed.");
}
catch (Exception e)
{
log.WarnFormat("Unhandled {0} on client callback: {1}", e.GetType(), e.Message);
}
SendProductChanged = false;
}
The SendProductChanged = false; line will take care of unbinding the event handler.
You can write a wrapper method which takes Actions of Funcs as parameters and you can use try catch blocks inside this function. You can call your functions using this function; something like:
public void CallMethod(Action methodToBeCalled)
{
try
{
methodToBeCalled();
}
catch
.....
....
}
Then call your functions like:
CallMethod(() => client.OnProductChanged(products));

Categories