Reputation: 3172
My code looks something like this
var userStartTask = LroMdmApiService.AddUser(user);
// .... do some stuff
await userStartTask;
When AddUser()
throws an exception, it bubbles up as a NullReferenceException
. It doesn't wait for await.
But if I structure the code like this...
var result = await LroMdmApiService.AddUser(user);
Then exceptions get caught properly. Can someone tell me what's going on here?
Here is complete code that shows the issue. What is the best practice for such a scenario?
class Program
{
private static void Main(string[] args)
{
CallAsync();
Console.ReadKey();
}
public static async void CallAsync()
{
var task = CallExceptionAsync();
ThrowException("Outside");
await task;
}
public static Task CallExceptionAsync()
{
return Task.Run(() =>
{
ThrowException("Inside");
});
}
public static void ThrowException(string msg)
{
throw new Exception(msg);
}
}
Upvotes: 12
Views: 20233
Reputation: 457197
This code
var result = await LroMdmApiService.AddUser(user);
is practically identical to this code:
var task = LroMdmApiService.AddUser(user);
var result = await task;
When AddUser() throws an exception, it bubbles up as a NullReferenceException. It doesn't wait for await.
AddUser
probably looks like this (where _service
is null
):
public static Task AddUser(User user)
{
return _service.AddUser(user);
}
This will cause a NullReferenceException
to be thrown directly, not placed on the returned task.
If you always want your exceptions to be placed on the returned tasks, then make every task-returning method async
:
public static async Task AddUser(User user)
{
return await _service.AddUser(user);
}
However, you should consider whether you really want to do that. NullReferenceException
in particular is a code bug; it's not an exception you should ever catch or care about in production. To use Eric Lippert's term, it's a boneheaded exception.
In my opinion, it doesn't matter where boneheaded exceptions are thrown - whether they're thrown directly or placed on a task - because these exceptions are only for the developer, not runtime.
Upvotes: 11
Reputation: 65702
Another thing to check is for Objects that are actually null.
This code was giving me a NullReferenceException
:
Plan plan = await db.Plans.FindAsync(id);
if (plan == null)
{
return HttpNotFound();
}
ViewBag.AdviserId = new SelectList(db.Advisers, "ID", "Name", plan.AdviserId);
PlanPage planPage = new PlanPage();
planPage.Plans.Add(plan); //The List<Plans> object hasn't been instantiated ( = new List<Plan>();)
return View(planPage);
Upvotes: 0
Reputation: 3172
I found the cause. Two exceptions were getting called. Before await and inside the task. The first ended the thread and returned execution back to the caller. So when the 2nd exception (from the task) was raised, it had no parent thread.
var userStartTask = LroMdmApiService.AddUser(user); //1) An exception was being thrown inside AddUser()
// .... do some stuff 2) And another exception was being thrown here
await userStartTask;
Exception thrown inside killed my app with the NullReferenceException bec/ the method it was called from no longer existed.
Since everybody is asking for examples, here is a simple example which shows the same issue.
class Program
{
private static void Main(string[] args)
{
CallAsync();
Console.ReadKey();
}
public static async void CallAsync()
{
var task = CallExceptionAsync();
ThrowException("Outside");
await task;
}
public static Task CallExceptionAsync()
{
return Task.Run(() =>
{
ThrowException("Inside");
});
}
public static void ThrowException(string msg)
{
throw new Exception(msg);
}
}
Upvotes: 5