我试图理解为什么在ASP中使用异步void方法.Net应用程序可能会导致以下异常,但异步任务似乎不会:

System.InvalidOperationException: An asynchronous module or handler 
completed while an asynchronous operation was still pending

我对中国的异步世界还比较陌生.NET,但我确实觉得我已经try 通过大量现有资源来运行这一功能,包括以下所有资源:

从这些资源中,我了解到最佳实践通常是返回Task并避免异步无效.我还理解异步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的理解是正确的,那么在这种情况下将async void视为"即发即忘"是不是有点错误,因为ASP.NET实际上并没有忘记这一点?

推荐答案

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

从历史上看,ASP.NET从.NET2.0开始就通过基于事件的异步模式(EAP)支持干净的异步操作,在EAP中,异步组件通知SynchronizationContext它们的开始和完成..NET4.5对此支持进行了第一次相当大的更改,更新了核心ASP.NET异步类型以更好地启用基于任务的异步模式(TAP,即async).

与此同时,每个不同的框架(WebForms、MVC等)都开发了自己的方式来与该核心进行交互,并将backwards compatibility作为优先事项.为了帮助开发人员,核心ASP.NETSynchronizationContext得到了增强,只有你看到的一个例外;它将捕获许多使用错误.

在WebForms世界中,它们有RegisterAsyncTask个事件处理程序,但很多人只使用async void个事件处理程序.所以ASP.NET SynchronizationContext将在页面生命周期的适当时间允许async void,如果您在不适当的时间使用它,它将引发该异常.

在MVC/WebAPI/SignalR世界中,这些框架更多地被 struct 化为服务.所以他们能够以一种非常自然的方式采用async Task,框架只需要处理返回的Task——一个非常干净的抽象.作为旁注,你不再需要AsyncController;MVC知道它是异步的,只是因为它返回Task.

但是,如果您try 返回Task and use async void,则不支持.几乎没有理由支持它;仅仅支持那些本来就不应该这样做的用户是相当复杂的.记住async void通知核心ASP.NET SynchronizationContext,完全绕过MVC框架.MVC框架知道如何等待Task,但它甚至不知道async void,所以它会将完成返回给ASP.NET core发现它并不完整.

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

  1. 你正在try 使用一些使用async void的图书馆或其他东西.抱歉,但显而易见的事实是,该库已损坏,必须修复.
  2. 您正在将EAP组件包装成Task,并正确使用await.这可能会导致问题,因为EAP组件直接与SynchronizationContext交互.在这种情况下,最好的解决方案是修改类型,使其自然支持分接,或者将其替换为分接类型(例如,HttpClient而不是WebClient).如果做不到这一点,您可以使用TAP-Over-APM而不是TAP-Over-EAP.如果这两种方法都不可行,那么您可以在Tap-over-EAP包装器周围使用Task.Run.

关于"火与遗忘":

我个人从来不在async void种方法中使用这个短语.首先,错误处理语义肯定不适合短语"fire and forget";我半Jest 地将async void种方法称为"火灾和碰撞".真正的async"fire and forget"方法是async Task方法,在这里,您忽略返回的Task而不是等待它.

也就是说,在ASP.NET你几乎永远都不想从请求中提前返回(这就是"开火并忘记"的含义).这个答案已经太长了,但如果真的有必要,我有description of the problems on my blog, along with some code to support ASP.NET "fire and forget"分.

Asp.net相关问答推荐

log4net 记录所有未处理的应用程序错误

如何在 ASP.NET WebForms 中实现 TDD

创建一个供 ASP.NET 应用程序中的所有线程使用的静态 Regex 对象是否有效?

在哪里可以记录 ASP.NET Core 应用程序的启动/停止/错误事件?

MSCharts找不到请求类型'GET'的http处理程序错误

ASP.NET GridView 第二个标题行跨越主标题行

Webapi、Webhost和Owin的关系

每个 'HttpRequest' 在 ASP.NET 中都有自己的线程吗?

尽管安装了 AspNetCoreModule,但在 IIS 中运行 ASP.NET Core 应用程序时出现 0x8007000d 错误 500.19

DBContext.Entry 有什么作用?

DropDownList AppendDataBoundItems(第一项为空白且无重复项)

如何在 C# 应用程序中调用 VBScript 文件?

ASP.NET Core 中的 NuGet 包位置在哪里?

如何从url中删除returnurl?

无法在 IIS 中启动网站 - W3SVC 正在运行

ASP .NET 单例

在资源文件中使用 HTML

HttpContext.Current属性的跨线程使用及相关的东西

ExecuteReader:连接属性尚未初始化

IronRuby死了吗?