asynchronous无效,ASP.Net和杰出操作计数

我想了解为什么ASP.Net应用程序中的asynchronous无效方法可能会导致以下exception,而似乎asynchronous任务不会:

System.InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending 

在.NET中,我对于async是一个相对陌生的东西,但是我觉得我已经试图通过一些现有的资源来运行这个资源,包括以下所有内容:

  • 返回void和返回一个Task有什么区别?
  • 这是所有关于SynchronizationContext
  • asynchronous语法糖build议
  • 在ASP.NET中asynchronous

从这些资源中,我明白最好的做法是典型地返回任务并避免asynchronous无效。 我也明白,async void在方法被调用时递增未完成操作的计数,并在完成时递减。 这听起来至less是我的问题的答案的一部分。 但是,我所缺less的是当我返回Task时会发生什么,为什么这样做会使事情“工作”。

这是一个人为的例子来进一步说明我的问题:

 public class HomeController : AsyncController { // This method will work fine public async Task<ActionResult> ThisPageWillLoad() { // Do not await the task since it is meant to be fire and forget var task = this.FireAndForgetTask(); return await Task.FromResult(this.View("Index")); } private async Task FireAndForgetTask() { var task = Task.Delay(TimeSpan.FromSeconds(3)); await task; } // This method will throw the following exception: // System.InvalidOperationException: An asynchronous module or // handler completed while an asynchronous operation was still pending public async Task<ActionResult> ThisPageWillNotLoad() { // Obviously can't await a void method this.FireAndForgetVoid(); return await Task.FromResult(this.View("Index")); } private async void FireAndForgetVoid() { var task = Task.Delay(TimeSpan.FromSeconds(3)); await task; } } 

在相关说明中,如果我对async void的理解是正确的,那么在这种情况下将async void看作“fire and forget”是不是有错误,因为ASP.Net实际上并没有忘记它呢?

在将async引入到ASP.NET中时,Microsoft决定尽可能避免向后兼容性问题。 他们想把它带到他们所有的“一个ASP.NET” – 所以对WinForms,MVC,WebAPI,SignalR等的async支持。

从历史上看,ASP.NET自.NET 2.0以来通过基于事件的asynchronous模式(EAP)支持干净的asynchronous操作,asynchronous组件通过SynchronizationContext通知SynchronizationContext的启动和完成。 .NET 4.5对此支持进行了首次相当大的更改,更新了核心ASP.NETasynchronoustypes以更好地启用基于任务的asynchronous模式(TAP,即async )。

与此同时,每个不同的框架(WebForms,MVC等)都开发了自己的方式与该核心进行交互,从而保持向后兼容的优先级。 为了帮助开发人员,除了你所看到的外,核心的ASP.NET SynchronizationContext得到了增强。 它会捕获许多使用错误。

在WebForms世界里,他们有RegisterAsyncTask但很多人只是使用async void事件处理程序。 所以ASP.NET的SynchronizationContext将允许在页面生命周期的适当时候async void ,如果你在不适当的时候使用它,它会引发exception。

在MVC / WebAPI / SignalR世界中,框架更像服务结构。 所以他们能够以非常自然的方式采用async Task ,并且框架只需要处理返回的Task – 一个非常干净的抽象。 作为一个方面说明,你不再需要AsyncController ; MVC知道它是asynchronous的,因为它返回一个Task

但是,如果您尝试返回Task 使用async void ,则不支持。 而且没有什么理由支持它。 支持那些不应该这样做的用户是相当复杂的。 请记住, async void直接通知核心ASP.NET SynchronizationContext ,完全绕过MVC框架。 MVC框架理解如何等待你的Task但它甚至不知道async void ,所以它返回完成到ASP.NET核心,它看起来并没有实际完成。

这可能会导致两种情况下的问题:

  1. 你正在尝试使用一些库或什么不使用async void 。 对不起,但是显而易见的事实是图书馆已经坏了,必须要修好。
  2. 您正在将EAP组件包装到Task并正确使用await 。 这可能会导致问题,因为EAP组件直接与SynchronizationContext交互。 在这种情况下,最好的解决scheme是修改types,使其自然支持TAP或用TAPtypes(例如, HttpClient而不是WebClient )replace它。 否则,您可以使用TAP-over-APM而不是TAP-over-EAP。 如果这两者都不可行,则可以在TAP-over-EAP包装器上使用Task.Run

关于“火与忘”:

我个人从来没有使用这个短语的async void方法。 首先,error handling语义当然不符合“fire and forget”这个词。 我半开玩笑地将async void方法称为“火灾和崩溃”。 一个真正的async “火和忘记”方法将是一个async Task方法,你忽略返回的Task而不是等待它。

也就是说,在ASP.NET中,你几乎不想从请求中提前返回(这就是“火和忘记”所暗示的)。 这个答案已经太长了,但是我对博客上的问题有了一个描述,还有一些代码支持ASP.NET,如果真的有必要的话,那就是“火和遗忘” 。