为什么异步等待抛出NullReferenceException?

时间:2015-09-01 16:24:19

标签: c# .net visual-studio asynchronous async-await

我的代码看起来像这样

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

AddUser()抛出异常时,它会以NullReferenceException冒泡。它不等待等待。

但如果我像这样构建代码......

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

3 个答案:

答案 0 :(得分:6)

此代码

var result = await LroMdmApiService.AddUser(user);

与此代码几乎完全相同:

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

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

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特别是代码错误;这不是你应该在生产中捕捉或关心的例外。 To use Eric Lippert's term, it's a boneheaded exception

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

要检查的另一件事是实际上为为空的对象。

这段代码给了我一个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);