我试图理解为什么ASP.Net应用程序中的异步void方法可能导致以下异常,而async Task似乎不会:
System.InvalidOperationException: An asynchronous module or handler
completed while an asynchronous operation was still pending
我对.NET中的异步世界相对较新,但我觉得我试图通过一些现有资源来运行这个,包括以下所有资源:
从这些资源中,我了解最佳做法是通常返回Task并避免异步void。我也理解async void会在调用方法时增加未完成操作的数量,并在完成时减少它。这听起来至少是我问题答案的一部分。但是,我遗漏的是当我返回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视为“火与忘”并不是错误的,因为ASP.Net实际上并没有忘记它吗?
答案 0 :(得分:57)
Microsoft决定在将async
引入ASP.NET时避免尽可能多的向后兼容性问题。他们想把它带到他们所有的“一个ASP.NET” - 所以async
支持WinForms,MVC,WebAPI,SignalR等。
从历史上看,ASP.NET通过基于事件的异步模式(EAP)支持自.NET 2.0以来的干净异步操作,其中异步组件通知SynchronizationContext
它们的启动和完成。 .NET 4.5为这种支持带来了第一次相当大的变化,更新了核心ASP.NET异步类型,以更好地启用基于任务的异步模式(TAP,即async
)。
与此同时,每个不同的框架(WebForms,MVC等)都开发了自己的方式与该核心进行交互,使向后兼容性成为优先事项。为了帮助开发人员,核心ASP.NET SynchronizationContext
得到了增强,除了你看到的例外;它会捕获许多使用错误。
在WebForms世界中,他们有RegisterAsyncTask
,但很多人只使用async void
事件处理程序。因此,ASP.NET SynchronizationContext
将在页面生命周期的适当时间允许async void
,如果您在不适当的时间使用它,则会引发该异常。
在MVC / WebAPI / SignalR世界中,框架更加结构化为服务。所以他们能够以非常自然的方式采用async Task
,而框架只需处理返回的Task
- 一个非常简洁的抽象。作为旁注,您不再需要AsyncController
; MVC知道它是异步的,因为它返回Task
。
但是,如果您尝试返回Task
并使用async void
,则不支持此功能。并没有理由支持它;只支持那些不应该这样做的用户,这将是非常复杂的。请记住,async void
直接通知核心ASP.NET SynchronizationContext
,完全绕过MVC框架。 MVC框架了解如何等待Task
,但它甚至不知道async void
,因此它将完成返回到ASP.NET核心,它看到它实际上不是 完成。
这可能会导致两种情况出现问题:
async void
的库或其他内容。对不起,但显而易见的事实是图书馆坏了,必须修复。Task
并正确使用await
。这可能会导致问题,因为EAP组件直接与SynchronizationContext
进行交互。在这种情况下,最好的解决方案是修改类型,使其自然支持TAP或用TAP类型替换它(例如,HttpClient
而不是WebClient
)。如果做不到这一点,您可以使用TAP-over-APM而不是TAP-over-EAP。如果这些都不可行,您可以在TAP-over-EAP包装器周围使用Task.Run
。关于“火与忘记”:
我个人从未在async void
方法中使用此短语。首先,错误处理语义肯定不符合“火与忘记”这个短语;我半开玩笑地将async void
方法称为“火灾和崩溃”。真正的async
“即发即弃”方法将是async Task
方法,您可以忽略返回的Task
而不是等待它。
也就是说,在ASP.NET中,你几乎不想从请求中提前返回(这就是“火与忘记”所暗示的)。这个答案已经太长了,但如果真的有必要,我会有description of the problems on my blog, along with some code to support ASP.NET "fire and forget"。