Async Void,ASP.Net和杰出运营计数

Dav*_*eps 47 c# asp.net async-await

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

System.InvalidOperationException: An asynchronous module or handler 
completed while an asynchronous operation was still pending
Run Code Online (Sandbox Code Playgroud)

我对.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Ste*_*ary 63

Microsoft决定在引入asyncASP.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 SynchronizationContextasync 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.如果这些都不可行,您可以使用Task.RunTAP-over-EAP包装器.

关于"火与忘记":

我个人从不使用这句话来表达async void方法.首先,错误处理语义肯定不符合"火与忘记"这个短语; 我半开玩笑地将async void方法称为"火灾和崩溃".真正的async"即发即弃"方法是一种async Task忽略返回Task而不是等待它的方法.

也就是说,在ASP.NET中,你几乎不想从请求中提前返回(这就是"火与忘记"所暗示的).这个答案已经太长了,但我对我博客上的问题进行描述,并提供了一些支持ASP.NET的代码"如果真的有必要的话就会忘记".