我试图理解为什么ASP.Net应用程序中的异步void方法会导致以下异常,而异步任务却不会:
System.InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending
我对.NET中的异步世界还比较陌生,但是确实感觉我已经尝试通过许多现有资源(包括以下所有资源)来简化此工作:
从这些资源中,我了解最佳实践是通常返回Task并避免异步void。我也了解,异步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; } }
在相关说明中,如果我对异步void的理解是正确的,那么在这种情况下将异步void视为“即发即弃”是不是错误的,因为ASP.Net实际上并没有忘记它?
Microsoft做出了避免在引入asyncASP.NET 时尽可能避免向后兼容性问题的决定。他们希望将其引入所有“一个ASP.NET”中-因此async支持WinForms,MVC,WebAPI,SignalR等。
async
从历史上看,ASP.NET从.NET 2.0开始就通过基于事件的异步模式(EAP)支持干净的异步操作,其中异步组件将SynchronizationContext其开始和完成通知给。.NET 4.5对该支持进行了第一个相当大的更改,更新了核心ASP.NET异步类型以更好地启用基于任务的异步模式(TAP,即async)。
SynchronizationContext
同时,每个不同的框架(WebForm,MVC等)都开发了自己的与该内核进行交互的方式,将 向后兼容性 作为优先事项。为了帮助开发人员,SynchronizationContext除了您看到的异常之外,还增强了核心ASP.NET 。它将捕获许多使用错误。
在WebForms世界中,他们有,RegisterAsyncTask但是很多人只使用async void事件处理程序。因此,ASP.NET SynchronizationContext将async void在页面生命周期的适当时间允许访问,如果在不适当的时间使用它,则会引发该异常。
RegisterAsyncTask
async void
在MVC / WebAPI / SignalR世界中,框架被结构化为服务。因此,他们能够以async Task非常自然的方式采用,并且该框架只需要处理返回的内容Task-一个非常干净的抽象。附带说明,您不再需要AsyncController了。MVC知道它是异步的,因为它返回Task。
async Task
Task
AsyncController
但是,如果您尝试返回a Task 并 使用async void,则不支持。而且没有什么理由支持它。仅支持不应该这样做的用户将是非常复杂的。请记住,它async void会SynchronizationContext直接绕过MVC框架直接通知核心ASP.NET 。MVC框架了解如何等待您,Task但它甚至不了解async void,因此它将完成返回到ASP.NET核心,该核心看到它 实际上 尚未完成。
在两种情况下,这可能会导致问题:
await
HttpClient
WebClient
Task.Run
关于“忘了忘了”:
我个人从不使用此短语作为async void方法。一方面,错误处理的语义最肯定不适合短语“即发即弃”。我开玩笑地将async void方法称为“火灾和崩溃”。真正的async“解雇”方法将是async Task您忽略返回Task而不是等待返回的方法。
就是说,在ASP.NET中,您几乎永远都不想从请求中早返回(这就是“一劳永逸”的含义)。这个答案已经太长了,但是我确实在博客上描述了这些问题,并在确实需要的情况下提供了一些支持ASP.NET“即弃即用”的代码。