异步太虚,ASP.Net,并计数未完成的操作的 [英] Async Void, ASP.Net, and Count of Outstanding Operations
问题描述
我试图理解为什么在ASP.Net应用程序异步无效方法可能导致以下异常,而似乎异步任务不会:
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:
System.InvalidOperationException: An asynchronous module or handler
completed while an asynchronous operation was still pending
我是比较新的.NET中异步的世界,但不要觉得我已经试图通过一些现有的资源下运行这一块,包括所有的以下内容:
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:
- What's 返回void和返回任务之间的区别?
- 这是所有关于SynchronizationContext的
- Async语法糖建议
- 异步ASP.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".
下面是一个人为的例子来进一步说明我的问题:
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;
}
}
在一个相关的说明,如果我的异步无效的理解是正确的,那么是不是一种错误的认为异步作废的是射后不理,在这种情况下,因为ASP.Net实际上没有忘记呢?
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?
推荐答案
微软将异步
到ASP时所做的决定,以避免尽可能多的向后兼容性问题成为可能。净。他们想将它推向所有的一ASP.NET的 - 所以异步为的WinForms,MVC,的WebAPI,SignalR等
支持
Microsoft made the decision to avoid as much backwards-compatibility issues as possible when bringing async
into ASP.NET. And they wanted to bring it to all of their "one ASP.NET" - so async
support for WinForms, MVC, WebAPI, SignalR, etc.
从历史上看,ASP.NET一直支持清洁异步操作,因为通过基于事件的异步模式(EAP),其中异步组件通知的的SynchronizationContext
.NET 2.0的启动和完成。 .NET 4.5带来的这种支持的第一个还算丰厚的变化,更新了核心ASP.NET异步类型,以更好地使基于任务的异步模式(TAP,即异步
)
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 SynchronizationContext
of 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
).
在此期间,每一个不同的框架(的WebForms,MVC等)都发展自己的方式与核心互动,保持的向后兼容性的一个优先事项。在试图帮助开发人员,核心ASP.NET 的SynchronizationContext
与您所看到的异常增强;它会赶上很多用法错误。
In the meantime, each different framework (WebForms, MVC, etc) all developed their own way to interact with that core, keeping backwards compatibility a priority. In an attempt to assist developers, the core ASP.NET SynchronizationContext
was enhanced with the exception you're seeing; it will catch many usage mistakes.
在WebForms的世界里,他们有 RegisterAsyncTask
,但很多人只是使用异步无效
事件处理程序来代替。因此,ASP.NET 的SynchronizationContext
将允许异步无效
在页面生命周期中适当的时候,如果你用它不适当的时候它会产生一个例外。
In the WebForms world, they have RegisterAsyncTask
but a lot of people just use async void
event handlers instead. So the ASP.NET SynchronizationContext
will allow async void
at appropriate times during the page lifecycle, and if you use it at an inappropriate time it will raise that exception.
在MVC /的WebAPI / SignalR世界,框架是为服务更有条理。因此,他们能够采取异步任务
在一个非常自然的方式,只需要处理返回的工作
- 一个非常干净的抽象。作为一个侧面说明,你不需要 AsyncController
了; MVC知道它是异步的,只是因为它返回一个工作
。
In the MVC/WebAPI/SignalR world, the frameworks are more structured as services. So they were able to adopt async Task
in 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 AsyncController
anymore; MVC knows it's asynchronous just because it returns a Task
.
然而,如果你试图返回工作
的和的使用异步无效
,这不是支持。还有什么理由来支持它;这将是非常复杂的只是支持不应该用户做的反正。请记住,异步无效
ASP.NET 的SynchronizationContext
直接通知核心,完全绕过了MVC框架。 MVC框架知道如何等待你的工作
,但它甚至不知道关于异步无效
,所以它返回完成到ASP.NET核心,认为它不是的真正的完整。
However, if you try to return a Task
and use 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 void
notifies the core ASP.NET SynchronizationContext
directly, bypassing the MVC framework completely. The MVC framework understands how to wait for your Task
but it doesn't even know about the async void
, so it returns completion to the ASP.NET core which sees that it's not actually complete.
这可能会导致问题的两种方案:
This can cause problems in two scenarios:
- 您正在尝试使用,使用
异步无效
一些图书馆或诸如此类的东西。遗憾,但明显的事实是,该库被打破,并且必须是固定的。 - 您正在包装一个EAP组件到
工作
并正确使用的await
。因为EAP组件与的SynchronizationContext
直接交互,这可能会导致问题。在这种情况下,最好的解决办法是那么它支持TAP自然修改类型或使用TAP类型更换它(例如,的HttpClient
而不是Web客户端
)。如果做不到这一点,你可以使用TAP-过APM代替TAP-过EAP的。如果没有那些是可行的,你可以使用Task.Run
在你的TAP-过EAP包装。
- 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
Task
and properly usingawait
. This can cause problems because the EAP component interacts withSynchronizationContext
directly. 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.,HttpClient
instead ofWebClient
). Failing that, you can use TAP-over-APM instead of TAP-over-EAP. If neither of those are feasible, you can just useTask.Run
around your TAP-over-EAP wrapper.
关于射后不理:
我个人从来不使用这个短语异步无效
方法。一方面,错误处理语义肯定不适合与短语射后不理;我半开玩笑地指异步无效
方法为火和崩溃。一个真正的异步
射后不理的方法是,你忽略了返回<$ C $的异步任务
方法C>工作,而不是等待它。
I personally never use this phrase for async void
methods. 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 void
methods as "fire and crash". A true async
"fire and forget" method would be an async Task
method where you ignore the returned Task
rather than waiting for it.
这是说,在你ASP.NET几乎从来没有想从请求提前返回(这就是所谓的射后不理暗示)。这个答案已经太久了,但我的问题说明我的博客,一些code一起为支持ASP.NET射后不理,如果它是真正必要的。
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,并计数未完成的操作的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!