为什么async await抛出NullReferenceException ?

本文关键字:NullReferenceException 抛出 await async 为什么 | 更新日期: 2023-09-27 18:10:47

我的代码看起来像这样

var userStartTask = LroMdmApiService.AddUser(user);
 // .... do some stuff
await userStartTask;

AddUser()抛出异常时,它会冒泡为NullReferenceException。它不需要等待await

但是如果我像这样构造代码…

var result = await LroMdmApiService.AddUser(user);

然后异常被正确捕获。谁能告诉我这是怎么回事?

下面是显示该问题的完整代码。这种场景的最佳实践是什么?

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

为什么async await抛出NullReferenceException ?

此代码

var result = await LroMdmApiService.AddUser(user);

实际上与以下代码相同:

var task = LroMdmApiService.AddUser(user);
var result = await task;

当AddUser()抛出异常时,它会以NullReferenceException的形式弹出。它不需要等待await

AddUser可能看起来像这样(其中_servicenull):

public static Task AddUser(User user)
{
  return _service.AddUser(user);
}

这将导致直接抛出 NullReferenceException,而不是放在返回的任务上。

如果您总是希望将异常放置在返回的任务上,那么将每个任务返回方法设置为async:

public static async Task AddUser(User user)
{
  return await _service.AddUser(user);
}
但是,您应该考虑是否真的希望这样做。NullReferenceException是一个代码bug;这不是您应该在生产环境中捕获或关心的异常。用Eric Lippert的话来说,这是一个愚蠢的例外。

在我看来,在哪里抛出愚蠢的异常并不重要——无论是直接抛出还是放在任务上——因为这些异常只针对开发人员,而不是运行时。

我找到原因了。调用了两个异常。在等待之前和任务内部。第一个线程结束线程并将执行返回给调用者。因此,当第二个异常(来自任务)被抛出时,它没有父线程。

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;

内部抛出的异常用NullReferenceException杀死了我的应用程序,因为它被调用的方法不再存在。

既然每个人都在问例子,这里有一个简单的例子来说明同样的问题。

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

另一件要检查的事情是实际上是 null的对象。

这个代码给了我一个NRE:

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