Async Void、ASP.Net 和未完成操作计数
Posted
技术标签:
【中文标题】Async Void、ASP.Net 和未完成操作计数【英文标题】:Async Void, ASP.Net, and Count of Outstanding Operations 【发布时间】:2013-07-13 15:13:03 【问题描述】:我试图了解为什么 ASP.Net 应用程序中的 async void 方法会导致以下异常,而 async Task 似乎不会:
System.InvalidOperationException: An asynchronous module or handler
completed while an asynchronous operation was still pending
我对 .NET 中的异步世界比较陌生,但我确实觉得我已经尝试通过许多现有资源来运行这个,包括以下所有资源:
What's the difference between returning void and returning a Task? It's All About the SynchronizationContext Async Syntactic Sugar Suggestions Async in ASP.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 实际上并没有忘记它?
【问题讨论】:
async void
是“一劳永逸”,因为你不能 await
它,因为它不会返回等待的 Task
。
【参考方案1】:
Microsoft 决定在将async
引入 ASP.NET 时尽可能避免出现向后兼容性问题。他们想把它带到他们所有的“一个 ASP.NET”——所以async
支持 WinForms、MVC、WebAPI、SignalR 等。
从历史上看,自 .NET 2.0 起,ASP.NET 通过基于事件的异步模式 (EAP) 支持干净的异步操作,其中异步组件通知 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
的库。抱歉,但事实是图书馆坏了,必须修复。
您将 EAP 组件包装到 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"。
【讨论】:
火灾和崩溃......很好。感谢您的详细回复。对我来说仍然缺少的一件是当我执行“var task = this.FireAndForgetTask()”时会发生什么。框架实际上对返回的任务做了什么?我不会将它存储在任何地方,也不会等待它。在这种情况下,是不是更接近于火而忘却?由于我们不在 EAP 领域,因此没有直接与 SynchronizationContext 交互的代码,并且不等待任务真的会忘记它吗? 如果你从来没有使用过task
,那么就没有任何用处。该框架甚至不知道它的存在。是的,这是“一劳永逸”,但请注意我的博客文章中关于这样做的警告。
优秀。我想我现在明白了。感谢您的回答和其他有用的链接。
@DavidKreps - 我也有类似的情况。你能告诉我你做了什么来实现上述目标吗?谢谢
@Ditty - 我想我不明白你的问题。您希望对上面所写内容的哪一部分进行澄清?以上是关于Async Void、ASP.Net 和未完成操作计数的主要内容,如果未能解决你的问题,请参考以下文章
asp.net webform中使用async,await实现异步操作
asp.net webform中使用async,await实现异步操作
为啥总是在 asp.net mvc 中同步异步操作(async await)
async / await 如何在 ASP.Net 应用程序中提供帮助?