本文介绍了异步无效、ASP.Net 和未完成操作的计数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图理解为什么 ASP.Net 应用程序中的 async void 方法会导致以下异常,而 async Task 似乎不会:

System.InvalidOperationException:异步模块或处理程序在异步操作仍待处理时完成

我对 .NET 中的异步世界相对较新,但确实感觉我已经尝试通过许多现有资源来运行这个,包括以下所有资源:

从这些资源中,我了解到最佳做法是通常返回 Task 并避免异步无效.我也明白 async void 在调用方法时增加未完成操作的计数,并在完成时减少它.这听起来至少是我问题的部分答案.但是,我缺少的是返回 Task 时会发生什么以及为什么这样做会使事情工作".

这是一个人为的例子来进一步说明我的问题:

public class HomeController : AsyncController{//这个方法可以正常工作公共异步任务ThisPageWillLoad(){//不要等待任务,因为它意味着火和忘记var task = this.FireAndForgetTask();return await Task.FromResult(this.View("Index"));}私有异步任务 FireAndForgetTask(){var task = Task.Delay(TimeSpan.FromSeconds(3));等待任务;}//此方法将抛出以下异常://System.InvalidOperationException: 一个异步模块或//处理程序已完成,而异步操作仍处于挂起状态公共异步任务ThisPageWillNotLoad(){//显然不能等待 void 方法this.FireAndForgetVoid();return await Task.FromResult(this.View("Index"));}私有异步 void FireAndForgetVoid(){var task = Task.Delay(TimeSpan.FromSeconds(3));等待任务;}}

在相关说明中,如果我对 async void 的理解是正确的,那么在这种情况下将 async void 视为即发即弃"是不是有点错误,因为 ASP.Net 实际上并没有忘记它?

解决方案

Microsoft 决定在将 async 引入 ASP.NET 时尽可能避免向后兼容性问题.他们想把它带到他们所有的一个 ASP.NET"——所以 async 支持 WinForms、MVC、WebAPI、SignalR 等.

从历史上看,ASP.NET 从 .NET 2.0 开始通过基于事件的异步模式 (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 核心,该核心看到它是没有实际上完成.

这会在两种情况下导致问题:

  1. 您正在尝试使用一些使用 async void 的库或诸如此类的东西.抱歉,但显而易见的事实是库坏了,必须修复.
  2. 您正在将 EAP 组件包装到 Task 中并正确使用 await.这可能会导致问题,因为 EAP 组件直接与 SynchronizationContext 交互.在这种情况下,最好的解决方案是修改类型,使其自然支持 TAP 或将其替换为 TAP 类型(例如,HttpClient 而不是 WebClient).否则,您可以使用 TAP-over-APM 而不是 TAP-over-EAP.如果这些都不可行,您可以在 TAP-over-EAP 包装器周围使用 Task.Run.

关于即发即弃":

我个人从不将这个短语用于 async void 方法.一方面,错误处理语义肯定不适合短语即发即忘";我半开玩笑地将 async void 方法称为fire and crash".真正的async即发即弃"方法应该是一种async Task 方法,您可以忽略返回的Task 而不是等待它.>

也就是说,在 ASP.NET 中,您几乎从不想从请求中提前返回(这就是即发即忘"的含义).这个答案已经太长了,但我在我的博客上有一个 问题描述,以及一些支持 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

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:

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;
    }
}

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?

解决方案

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.

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).

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.

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.

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.

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:

  1. 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.
  2. You're wrapping an EAP component into a Task and properly using await. This can cause problems because the EAP component interacts with SynchronizationContext 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 of WebClient). Failing that, you can use TAP-over-APM instead of TAP-over-EAP. If neither of those are feasible, you can just use Task.Run around your TAP-over-EAP wrapper.


Regarding "fire and forget":

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.

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 和未完成操作的计数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-27 19:01