【问题标题】:Web Api throwing Exception on async methodWeb Api 在异步方法上抛出异常
【发布时间】:2014-05-15 09:19:53
【问题描述】:

我有一个 Web API 方法,它调用另一个标有 async 的方法来更新数据库(使用 EF 6)。我不需要等待 db 方法完成(它触发并忘记),因此在调用此异步方法时我不使用 await。如果我不调用await,则会抛出一个从未传递给我的代码的 NullReferenceException,它只会在 VS2013 的输出窗口中显示为第一次机会异常。

在没有await-ing 的情况下调用异步方法的正确方法是什么?

下面是我正在做的一个例子:

数据回购方式:

public async Task UpdateSessionCheckAsync(int sessionId, DateTime time)
    {

        using (MyEntities context = new MyEntities())
        {
            var session = await context.Sessions.FindAsync(sessionId);

            if (session != null)
                session.LastCheck = time;

            await context.SaveChangesAsync();
        }

    }

Web api 存储库方法:

public async Task<ISession> GetSessionInfoAsync(int accountId, int siteId, int visitorId, int sessionId)
    {
        //some type of validation

        var session =await  GetValidSession(accountId, siteId, visitorId, sessionId);

        DataAdapter.UpdateSessionCheckAsync(sessionId, DateTime.UtcNow); // this is the line causing the exception if not awaited

        return new Session
        {
            Id = sessionId,
            VisitorId = session.VisitorId
        };

    }

Web API 方法:

 [ResponseType(typeof(Session))]
    public async Task<IHttpActionResult> GetSessionInfo(HttpRequestMessage request, int accountId, int siteId, int visitorId, int sessionId)
    {
            var info = await _repository.GetSessionInfoAsync(accountId, siteId, visitorId, sessionId);

            return Ok(info);
    }

最后是我得到的堆栈跟踪:

    System.Web.dll!System.Web.ThreadContext.AssociateWithCurrentThread(bool setImpersonationContext)
System.Web.dll!System.Web.HttpApplication.OnThreadEnterPrivate(bool setImpersonationContext)
System.Web.dll!System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
System.Web.dll!System.Web.Util.SynchronizationHelper.SafeWrapCallback(System.Action action)
System.Web.dll!System.Web.Util.SynchronizationHelper.QueueAsynchronous.AnonymousMethod__7(System.Threading.Tasks.Task _)
mscorlib.dll!System.Threading.Tasks.ContinuationTaskFromTask.InnerInvoke()
mscorlib.dll!System.Threading.Tasks.Task.Execute()
mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj)
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot)
mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution)
mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

编辑: 我已经将我的db方法更改为返回void而不是task,然后使用Task.Factory启动一个调用db方法的新任务并且异常消失了。

public async Task<ISession> GetSessionInfoAsync(int accountId, int siteId, int visitorId, int sessionId)
    {
        //some type of validation

        var session =await  GetValidSession(accountId, siteId, visitorId, sessionId);

        Task.Factory.StartNew(() => DataAdapter.UpdateSessionCheckAsync(sessionId, DateTime.UtcNow));

        return new Session
        {
            Id = sessionId,
            VisitorId = session.VisitorId
        };

    }

【问题讨论】:

标签: c# asp.net-web-api async-await


【解决方案1】:

我不需要等待 db 方法完成(它的触发和忘记)

当我看到这个时,我做的第一件事总是问:你确定要这样做吗?

请记住,在 ASP.NET 上“一劳永逸”实际上与说“我不在乎这段代码是否实际执行过”是一样的。

由于您只是在更新“最后一次检查”时间,因此您可能需要“即发即弃”;请注意,这意味着您的最后一次检查时间可能正确,也可能不正确。

也就是说,您可以使用 ASP.NET 运行时as I describe on my blog 注册您的工作:

public async Task<ISession> GetSessionInfoAsync(int accountId, int siteId, int visitorId, int sessionId)
{
  //some type of validation
  var session = await GetValidSession(accountId, siteId, visitorId, sessionId);
  BackgroundTaskManager.Run(() => DataAdapter.UpdateSessionCheckAsync(sessionId, DateTime.UtcNow));

  return new Session
  {
    Id = sessionId,
    VisitorId = session.VisitorId
  };
}

请注意,这是假设 UpdateSessionCheckAsync 确实返回 Task

特别是:

  • async void 方法具有非常尴尬的错误处理语义。如果写入数据库时​​出现问题,async void 方法会默认使您的应用程序崩溃。
  • Task.Factory.StartNew is rather dangerous,正如我在博客中解释的那样。

【讨论】:

  • 谢谢斯蒂芬。这些博文正是我所需要的。
  • @Vlado:链接对我来说很好用。你的意思是我博客上的代码不起作用?
  • 这个异步无效有什么问题?我刚刚遇到了同样的问题:如果出现异常,整个 Web 应用程序都会死掉,只是将其更改为 async Task 就完全解决了问题,我真的不明白为什么 async void 应该与 async Task 不同......
  • @IlyaChernomordik:从async 方法的概念来考虑。如果它以异常完成,并且该方法返回Task,那么该异常可以放在Task 上。如果它以异常完成,但方法返回void,该怎么办?唯一真正的选择是 1)默默地忽略异常(一个可怕的想法),或 2)引发异常,就好像它是该上下文的“顶级”异常一样。 async void 确实 (2)。
  • 嗯,async/await 应该是透明的,对吧?如果您对 void 进行了普通操作,则将正确处理异常。所以我认为保持这种行为是有意义的,否则 async void 似乎毫无用处,所以我们应该总是使用 async Task 来代替,在这种情况下它可以隐式完成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-02
  • 2016-07-10
  • 2015-04-15
  • 1970-01-01
  • 2012-11-22
  • 1970-01-01
  • 2018-04-09
相关资源
最近更新 更多