Reputation: 1276
I was trying to explain to a colleague why async void
functions are bad and that exceptions won't get caught, but it turns out I might not be understanding them right. We have a piece of code that looks a bit like this:
public ActionResult EmailCandidates(List<string> identityTokens, string subject, string content)
{
// generate list of recipients here
SendEmail(recipients, subject, content); //not awaited
return new AjaxResponse { // AjaxResponse is a wrapper around JSONResponse
IsSuccess = true,
Data = recipients.Select(r=>r.Name)
};
}
private async void SendEmail(List<EmailAddress> recipients, string subject, string content)
{
await Task.Delay(10000); // simulate async send email
throw new Exception(); // manually added
}
What I was expecting, and what I was trying to explain is that if the SendEmail
function throws an exception it won't be caught properly because the main function EmailCandidates
has already returned to the client. Only that's not what happens. The code above executes in exactly the order I expect:
EmailCandidates
SendEmail
is calledEmailCandidates
, and the return is executedand then it gets kind of weird:
EmailCandidates
has returnedSo why even though EmailCandidates
has returned, does the response not get sent to the client. How does is know to wait for the async SendEmail
function?
Upvotes: 1
Views: 924
Reputation: 1251
Async void methods are kinda different beasts from the "normal" async methods.They have different error handling logic. When an exception is thrown out of an async Task
or async Task<T>
method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown by an async void method will be raised directly on the SynchronizationContext that was active when the async void method has been called. These exceptions can be observed using UnhandledException event handler.
Upvotes: 0
Reputation: 4119
Your application is working fine, actually is following the default behavior that MVC has. If you put explicitly the exception, then this kind of errors(500) arise when the request is originated from the same machine where the application is on(localhost) if you want to see what the actual user want to see you need to change the value on the webconfig for the By default it is set to RemoteOnly.
If you go to your FilterConfig. You will see this line of code
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
Try to change the value to "On" and you will see that you ended on that error page because the handler error attribute, it is providing post processing logic on an action and when it sees that an exception has escaped from an action it will display an error view instead of the yellow screen of death. The error View is on your application by default inside View/Shared/Error.cshtml
Your Off option
Specifies that custom errors are disabled. This allows display of detailed errors.
for reference go here: https://msdn.microsoft.com/en-us/library/h0hfz6fc(v=vs.71).aspx
If you put remote only then you will continue seeing the error if you are debugging the website in your localmachine, but if you host the application the final user will not see that error.
Upvotes: 0
Reputation: 456417
ASP.NET provides a SynchronizationContext
that does keep track of the number of asynchronous operations in flight, and will not send a result until they have all completed. Note that this SynchronizationContext
has been removed in ASP.NET Core.
However, you shouldn't be seeing this behavior even on ASP.NET. In the case of a synchronous method calling an async void
method, you should see an InvalidOperationException
with the message "An asynchronous operation cannot be started at this time.". In the case of an asynchronous method calling an async void
method (that doesn't complete before the handler returns), you should see an InvalidOperationException
with the message "An asynchronous module or handler completed while an asynchronous operation was still pending."
Since neither of these safety nets are triggering, I suspect that your ASP.NET code is using an old version of ASP.NET. Those safety nets were added in .NET 4.5, which you have to not only have as a build target but you also have to add targetFramework
in your web.config
.
A new .NET 4.5.2 ASP.NET MVC app with the following code immediately throws an InvalidOperationException
, as expected:
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
Test();
return View();
}
private async void Test()
{
await Task.Delay(20000);
throw new Exception("Blah");
}
Upvotes: 4