C# 异步无效、ASP.Net 和未完成操作的计数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17659603/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Async Void, ASP.Net, and Count of Outstanding Operations
提问by David Kreps
I am trying to understand why an async void method in an ASP.Net application can result in the following exception, while it appears that async Task will not:
我试图理解为什么 ASP.Net 应用程序中的 async void 方法会导致以下异常,而 async Task 似乎不会:
System.InvalidOperationException: An asynchronous module or handler
completed while an asynchronous operation was still pending
I am relatively new to the world of async in .NET, but do feel like I've tried to run this one down via a number of existing resources, including all of the following:
我对 .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
From these resources, I understand the best practice is to typically return Task and avoid async void. I also understand that async void increments the count of outstanding operations when the method is called and decrements it when it is completed. This sounds like at least part of the answer to my question. However, what I am missing is what happens when I return Task and why doing so makes things "work".
从这些资源中,我了解到最佳实践通常是返回 Task 并避免 async void。我也明白 async void 在调用方法时增加未完成操作的计数,并在完成时减少它。这听起来至少是我问题的部分答案。但是,我缺少的是返回 Task 时会发生什么以及为什么这样做会使事情“起作用”。
Here is a contrived example to further illustrate my question:
这是一个人为的例子来进一步说明我的问题:
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;
}
}
On a related note, if my understanding of async void is correct, then isn't it kind of wrong to think of async void as "fire and forget" in this scenario since ASP.Net is not actually forgetting about it?
在相关说明中,如果我对 async void 的理解是正确的,那么在这种情况下将 async void 视为“即发即弃”是不是有点错误,因为 ASP.Net 实际上并没有忘记它?
采纳答案by Stephen Cleary
Microsoft made the decision to avoid as much backwards-compatibility issues as possible when bringing asyncinto ASP.NET. And they wanted to bring it to all of their "one ASP.NET" - so asyncsupport for WinForms, MVC, WebAPI, SignalR, etc.
Microsoft 决定在引入asyncASP.NET时尽可能避免向后兼容性问题。他们想把它带到他们所有的“一个 ASP.NET”——所以async支持 WinForms、MVC、WebAPI、SignalR 等。
Historically, ASP.NET has supported clean asynchronous operations since .NET 2.0 via the Event-based Asynchronous Pattern (EAP), in which asynchronous components notify the SynchronizationContextof their starting and completing. .NET 4.5 brings the first fairly hefty changes to this support, updating the core ASP.NET asynchronous types to better enable the Task-based Asynchronous Pattern (TAP, i.e., async).
从历史上看,ASP.NET 从 .NET 2.0 开始通过基于事件的异步模式 (EAP) 支持干净的异步操作,其中异步组件通知SynchronizationContext它们的开始和完成。.NET 4.5 对此支持进行了第一次相当大的更改,更新了核心 ASP.NET 异步类型以更好地启用基于任务的异步模式 (TAP,即async)。
In the meantime, each different framework (WebForms, MVC, etc) all developed their own way to interact with that core, keeping backwards compatibilitya priority. In an attempt to assist developers, the core ASP.NET SynchronizationContextwas enhanced with the exception you're seeing; it will catch many usage mistakes.
与此同时,每个不同的框架(WebForms、MVC 等)都开发了自己的方式来与该核心交互,从而优先考虑向后兼容性。为了帮助开发人员,核心 ASP.NETSynchronizationContext得到了增强,但您看到的例外;它将捕获许多使用错误。
In the WebForms world, they have RegisterAsyncTaskbut a lot of people just use async voidevent handlers instead. So the ASP.NET SynchronizationContextwill allow async voidat appropriate times during the page lifecycle, and if you use it at an inappropriate time it will raise that exception.
在 WebForms 世界中,他们有RegisterAsyncTask但很多人只是使用async void事件处理程序。因此 ASP.NETSynchronizationContext将允许async void在页面生命周期中的适当时间,如果您在不适当的时间使用它,它将引发该异常。
In the MVC/WebAPI/SignalR world, the frameworks are more structured as services. So they were able to adopt async Taskin a very natural fashion, and the framework only has to deal with the returned Task- a very clean abstraction. As a side note, you don't need AsyncControlleranymore; MVC knows it's asynchronous just because it returns a Task.
在 MVC/WebAPI/SignalR 世界中,框架更结构化为服务。所以他们能够以async Task非常自然的方式采用,并且框架只需要处理返回的Task- 一个非常干净的抽象。作为旁注,您不再需要AsyncController;MVC 知道它是异步的,因为它返回一个Task.
However, if you try to return a Taskanduse async void, that's not supported. And there's little reason to support it; it would be quite complex just to support users that aren't supposed to be doing that anyway. Remember that async voidnotifies the core ASP.NET SynchronizationContextdirectly, bypassing the MVC framework completely. The MVC framework understands how to wait for your Taskbut it doesn't even know about the async void, so it returns completion to the ASP.NET core which sees that it's not actuallycomplete.
但是,如果您尝试返回 aTask并使用async void,则不受支持。没有什么理由支持它;仅仅支持那些无论如何都不应该这样做的用户会非常复杂。请记住,直接async void通知核心 ASP.NET SynchronizationContext,完全绕过 MVC 框架。MVC 框架了解如何等待您的,Task但它甚至不知道async void,因此它将完成返回给 ASP.NET 核心,后者认为它实际上并未完成。
This can cause problems in two scenarios:
这可能会在两种情况下导致问题:
- You're trying to use some library or whatnot that uses
async void. Sorry, but the plain fact is that the library is broken, and will have to be fixed. - You're wrapping an EAP component into a
Taskand properly usingawait. This can cause problems because the EAP component interacts withSynchronizationContextdirectly. In this case, the best solution is to modify the type so it supports TAP naturally or replace it with a TAP type (e.g.,HttpClientinstead ofWebClient). Failing that, you can use TAP-over-APM instead of TAP-over-EAP. If neither of those are feasible, you can just useTask.Runaround your TAP-over-EAP wrapper.
- 您正在尝试使用一些库或其他使用
async void. 抱歉,但显而易见的事实是图书馆坏了,必须修理。 - 您正在将 EAP 组件包装到 a 中
Task并正确使用await. 这可能会导致问题,因为 EAP 组件SynchronizationContext直接与之交互。在这种情况下,最好的解决方案是修改类型,使其自然支持 TAP 或将其替换为 TAP 类型(例如,HttpClient代替WebClient)。否则,您可以使用 TAP-over-APM 而不是 TAP-over-EAP。如果这些都不可行,您可以只使用Task.RunTAP-over-EAP 包装器。
Regarding "fire and forget":
关于“火与遗忘”:
I personally never use this phrase for async voidmethods. For one thing, the error handling semantics most certainly do not fit in with the phrase "fire and forget"; I half-jokingly refer to async voidmethods as "fire and crash". A true async"fire and forget" method would be an async Taskmethod where you ignore the returned Taskrather than waiting for it.
我个人从不将这个短语用于async void方法。一方面,错误处理语义肯定不适合短语“即发即忘”;我半开玩笑地将async void方法称为“火灾和崩溃”。真正的async“即发即弃”方法是一种async Task忽略返回值Task而不是等待返回值的方法。
That said, in ASP.NET you almost never want to return early from requests (which is what "fire and forget" implies). This answer is already too long, but I have a description of the problems on my blog, along with some code to support ASP.NET "fire and forget"if it's truly necessary.
也就是说,在 ASP.NET 中,您几乎从不想从请求中提前返回(这就是“即发即忘”的含义)。这个答案已经太长了,但我在我的博客上有问题的描述,以及一些支持 ASP.NET “即发即忘”的代码(如果确实有必要的话)。

