Skip to main content
 首页 » 编程设计

c#之Web Api 在异步方法上抛出异常

2024年02月01日25txw1958

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

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

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

数据 repo 方法:

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

网络 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.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 
        }; 
 
    } 

请您参考如下方法:

I do not need to wait on the db method to complete (its fire and forget)

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

请记住,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 ,正如我在我的博客上解释的那样。