Web Api在异步方法上抛出异常

时间:2014-04-03 13:35:52

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

我有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
        };

    }

1 个答案:

答案 0 :(得分:5)

  

我不需要等待db方法完成(它的火灾并忘记)

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

请记住,ASP.NET上的“fire and forget”与“我不关心这段代码是否真正被执行”实际上是一样的。

由于您只是更新“最后检查”时间,因此您可能会想到“即发即弃”;请注意,这意味着您的上次检查时间实际上可能正确也可能不正确。

也就是说,您可以使用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,正如我在博客上解释的那样。