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核心,它看起来并没有实际完成。
这可能会导致两种情况下的问题:
- 你正在尝试使用一些库或什么不使用
async void
。 对不起,但是显而易见的事实是图书馆已经坏了,必须要修好。 - 您正在将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,如果真的有必要的话,那就是“火和遗忘” 。