Problems with handling exceptions from async method with tasks - c#

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

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.
}
}

Stuck with async await thread

In constructor I want to call one method type :
private async Task OnLoadPrometDanKorisnikDatum
and I want to wait that method while its finish, and I have more method(3) like this and I want to call this 3 methods in background thread and don't wait him to finish, just want to wait first method. And I want to them executing parallel.
I have methods async Task,and in constructor of view model I call like this
OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat,
DatumVrednost).Wait();
OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja).Wait();
if I don't place .Wait() on the end, program doesn't work. I see in debug mode they run asynchronly, but time spent tell me that they sub(one method time + second method time + ....).
Can someone help me, this is for me very stuf...
Answer
The best way to handle your scenario is to use async void.
I recommend first reading the Explanation section below to fully understand the best practices around async void.
public MyConstructor()
{
ExecuteAsyncMethods();
}
async void ExecuteAsyncMethods()
{
try
{
await OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat, DatumVrednost);
await OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja);
}
catch(Exception e)
{
//Handle Exception
}
}
Explanation
Many C# devs are taught "Never use async void", but this is one of the few use-cases for it.
Yes async void can be dangerous and here's why:
Cannot await an async avoid method
Can lead to race conditions
Difficult to catch an Exception thrown by async void methods
E.g. the following try/catch block will not catch the Exception thrown here:
public MyConstructor()
{
try
{
//Cannot await `async void`
AsyncVoidMethodWithException();
}
catch(Exception e)
{
//Will never catch the `Exception` thrown in `AsyncVoidMethodWithException` because `AsyncVoidMethodWithException` cannot be awaited
}
//code here will be executing by the time `AsyncVoidMethodWithException` throws the exception
}
async void AsyncVoidMethodWithException()
{
await Task.Delay(2000);
throw new Exception();
}
That being said, as long as we wrap the contents of our entire async void in a try/catch block, we will be able to catch the exception, like so:
public MyConstructor()
{
AsyncVoidMethodWithException();
}
async void AsyncVoidMethodWithException()
{
try
{
await Task.Delay(2000);
throw new Exception();
}
catch(Exception e)
{
//Exception will be caught and successfully handled
}
}
SafeFireAndForget
I created a library to help with this and its additional benefit is that it avoids writing async void code that could be potentially misused by future devs.
It's open source and also available on NuGet:
Source Code
NuGet Package
SafeFireAndForget
SafeFireAndForget allows us to safely execute a Task whilst not blocking the calling thread and without waiting for it to finish before moving to the next line of code.
Below is a simplified version of SafeFireAndForget that you can add to your project.
However, I recommend copy/pasting its complete source code or adding its NuGet Package to your library to get a more robust implementation
public static async void SafeFireAndForget<TException>(this Task task, Action<TException> onException = null, bool continueOnCapturedContext = false) where TException : Exception
{
try
{
await task.ConfigureAwait(continueOnCapturedContext);
}
catch (TException ex) when (onException != null)
{
onException(ex);
}
}
Using SafeFireAndForget
To use SafeFireAndForget, append it to your method call like so:
OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat, DatumVrednost).SafeFireAndForget();
OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja).SafeFireAndForget();
To handle any Exception thrown by that Task, use onException. Here's an example that prints the Exception to the Debug Console:
OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat, DatumVrednost).SafeFireAndForget(ex => Debug.WriteLine(ex));
OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja).SafeFireAndForget(ex => Debug.WriteLine(ex));

Unable to catch an Exception from Task.Run

I am following this MSDN guide to handle the exceptions within a Task.
This is what I wrote:
var myTask = Task.Run(() =>
{
throw new Exception("test");
});
try
{
myTask.Wait();
}
catch (Exception e)
{
return false;
}
I have set a breakpoint within the catch block, but at debug runtime, the code does not reach the breakpoint, and it's giving me:
Exception is unhandled by user code
I have no idea what is going on as I have followed very closely to the example from the MSDN guide. In fact, I copied the example to my project and it's still giving the same problem.
Is there any method I can handle the exception outside the Task? I need to return a boolean value based on the fact if the task throws any Exception or not.
Edit
To make it clearer for some of you, this is a more complete set of codes:
public bool ConnectToService()
{
try
{
// Codes for ServiceHost etc etc, which I'm skipping
// These codes are already commented out for this test, so they do nothing
var myTask = Task.Run(() =>
{
// Supposed to connect to a WCF service, but just throwing a test exception now to simulate what happens when the service is not running
throw new Exception("test");
});
try
{
myTask.Wait();
}
catch (Exception e)
{
return false;
}
return true;
}
catch (Exception)
{
return false;
}
}
Caller:
public void DoSomething()
{
try
{
// Other irrelevant stuff
if (ConnectToService())
{
DoAnotherThing();
}
}
catch (Exception)
{
}
}
I would also want to point out I have a solution for this, but it's puzzling why an example from MSDN isn't working for me. I would think that my own solution is not elegant, so I'm still looking for a more elegant solution.
Exception taskException = null;
var myTask = Task.Run(() =>
{
try
{
throw new Exception("test");
}
catch (Exception e)
{
taskException = e;
}
});
try
{
myTask.Wait();
if (taskException != null) throw taskException;
}
catch (Exception e)
{
return false;
}
When a task is run, any exceptions that it throws are retained and re-thrown when something waits for the task's result or for the task to complete
task.Wait() Rethrows any exceptions
task.Result Rethrows any exceptions
As well, your code works correctly
Just press f5 while catching an exception and you will see that will get your point
According to MSDN Task.Run:
Queues the specified work to run on the thread pool and returns a Task object that represents that work.
So You throwing your exception on different thread than you trying to catch it. You should deal with exception on same thread.
Alternatively you can deal with unhandled exceptions in global AppDomain.UnhandledException event.
Jai, as mentioned, this code will always work. I think you will have to enable some settings in visual studio. The setting is turned off and because of this, you are getting "Exception not handled by user code".
try checking Under Tools, Options, Debugging, General, Enable just my code.
Also, you can use something like below if you don't like to bother about try/catch stuff :
myTask.ContinueWith(<you can access Exception property here to see if there was an exception>)
I had the same Problem and solved with ContinueWith
See:
var task = Task.Run(() =>
{
ChatHubWrapper chatHub = Ordem_ServicoBLL.sendMensagemIniciarChatPelaVr(pessoaWrapper.OrdemServico);
foreach (var mensagem in chatHub.MensagensEnviadas)
ChatHub.sendMensagemTodaSala(pessoaWrapper.OrdemServico.ID, mensagem);
})
.ContinueWith((t) =>
{
if (t.IsFaulted)
setPanelErrorWhats(t.Exception.InnerException.Message); // or throw new Exception...
});
task.Wait();
if (task.IsCompleted)
Response.Redirect(pessoaWrapper.OrdemServico.getUrlViewOSSuporte());
With this you Don't need a create Exception taskException = null;
And is not good to use catch Inside Task.Run
#Jai, please try to move a Task.Run to the inside of try/catch block. I think Task.Run executes imediatelly so you may get exception because of that.

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.

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