Debugging exception through multiple nested async calls - c#

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.

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 Aggregate Exceptions in Service Fabric

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 :-)

Task swallows the exception thrown

In the method below, when an exception is thrown in the TRY block, it is being swallowed. How can I make it throw the exception so that it gets written to log in the catch block? The log writer works fine. Thanks!
public static bool MonitorQueueEmptyTask(string queueName, CancellationTokenSource tokenSource)
{
try
{
Task<bool> task = Task.Factory.StartNew<bool>(() =>
{
while (!QueueManager.IsQueueEmpty(queueName))
{
if (tokenSource.IsCancellationRequested)
{
break;
}
Thread.Sleep(5000);
throw new Exception("Throwing an error!"); //THIS THROW IS SWALLOWED -- NO LOG WRITTEN ON CATCH
};
return true;
}, tokenSource.Token);
}
catch (Exception ex)
{
WriteExceptionToLog(ex.Stack); //it's not that this method doesn't work. it works fine.
return false;
}
return true;
}
If you want to fire and forget, you can attach a continuation using ContinueWith. The current try-catch will not help you at all, as the exception is encapsulated inside the Task. If this is "fire and forget", than you can log the exception:
public static Task MonitorQueueEmptyTask(
string queueName, CancellationTokenSource tokenSource)
{
return Task.Factory.StartNew<bool>(() =>
{
while (!QueueManager.IsQueueEmpty(queueName))
{
if (tokenSource.IsCancellationRequested)
{
break;
}
Thread.Sleep(5000);
throw new Exception("Throwing an error!");
};
}, tokenSource.Token, TaskCreationOptions.LongRunning).ContinueWith(faultedTask =>
{
WriteExceptionToLog(faultedTask.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
}
This, in turn, will not propagate the exception after it's thrown, but will provide a mechanism to log the error. If you want the exception to be properly handled, you can register to TaskScheduler.UnobservedTaskException. Additionally, you can set ThrowUnobservedTaskExceptions enabled="true" in your configuration if you want unhandled exceptions to terminate your application. ContinueWith will consider the exception "handled" once you look at the task.Exception property.
The exception is not swallowed; it's just that it doesn't occur on the thread that executes the try/catch block, but on the separate Task thread.
If you don't observe the task's result or exception, when the task is eventually garbage collected, it will throw an exception saying that the task was not observed. Unless you catch that by handling the TaskScheduler.UnobservedTaskException, it will crash the process.
I also had a problem with this, and i really dislike the whole idea of App.config, so can provide another solution to prevent the exceptions disappearing :)
Save the exception then throw it after the Task.Run has completed, e.g.
private async void Function() {
Exception save_exception = null;
await Task.Run(() => {
try {
// Do Stuff
} catch (Exception ex) {
save_exception = ex;
}
}).ContinueWith(new Action<Task>(task => {
if (save_exception != null)
throw save_exception;
// Do Stuff
}));
}

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

Prevent Task.ContinueWith on exception

I am trying to prevent a task from continuing if the first part fails.
My code looks like that:
Task listener = Task.Factory.StartNew(openConnection).ContinueWith((t) => listenForNumber());
void openConnection()
{
try
{
//stuff
}
catch
{
//morestuff
}
}
void listenForNumber()
{
//even more stuff
}
Now listenForNuber() should not be executed if openConnection() enters the catch block
I tried ContinueWith((t) => listenForNumber(),TaskContinuationOptions.NotOnFaulted);
But no success, any help? :(
Thanks
TaskContiuationOptions.NotOnFaulted will obviously have no effect unless your method has faulted, i.e. an exception thrown during its execution was unhandled.
In your catch block, you should re-throw the exception (and preserve the stack trace) using the throw; statement after you've performed your work (some clean-up maybe) - otherwise the exception won't be thrown again, so your method will not be considered as 'faulted'.
Create an extension method helper.
public static void PropagateExceptions(this Task task)
{
if (task == null)
throw new ArgumentNullException("task");
if (!task.IsCompleted)
throw new InvalidOperationException("The task has not completed yet.");
if (task.IsFaulted)
task.Wait();
}
then call PropagateExceptions() extension method before executing any codes. PropagateExceptions() method will also rethrow if the task was cancelled.
t1.ContinueWith(t => {
t.PropagateExceptions();
listenForNumber();
});
You need to throw the exception in your task method. The TPL does not know the method has failed, unless it catches an exception.
You will still need to have a continuation method for the faulted case. This could be a simple method that logs the exception.
If you don't have a continuation method for the exception, you will get unhandled exceptions in your application when your task method throws an exception.

Categories