【问题标题】:MVC function returns but waits for async function before sending resultMVC 函数返回但在发送结果之前等待异步函数
【发布时间】:2017-07-12 20:06:15
【问题描述】:

我试图向一位同事解释为什么async void 函数不好并且不会捕获异常,但事实证明我可能没有正确理解它们。我们有一段代码看起来有点像这样:

public ActionResult EmailCandidates(List<string> identityTokens, string subject, string content)
{
    // generate list of recipients here
    SendEmail(recipients, subject, content); //not awaited
    return new AjaxResponse { // AjaxResponse is a wrapper around JSONResponse
        IsSuccess = true,
        Data = recipients.Select(r=>r.Name)
    };
}

private async void SendEmail(List<EmailAddress> recipients, string subject, string content)
{
    await Task.Delay(10000); // simulate async send email
    throw new Exception(); // manually added
}

我所期待的,并且我试图解释的是,如果 SendEmail 函数抛出异常,它将不会被正确捕获,因为主函数 EmailCandidates 已经返回给客户端。只是这不是发生的事情。上面的代码完全按照我期望的顺序执行:

  • 从客户端调用EmailCandidates
  • SendEmail 被调用
  • 电子邮件是异步发送的(此处通过异步等待模拟)
  • 控制返回EmailCandidates,并执行返回

然后它变得有点奇怪:

  • 此时,我希望收到客户的回复,但我没有,即使 EmailCandidates 已返回
  • 10 秒后抛出异常
  • 异常被全局错误处理程序捕获,现在客户端收到 500 错误(不出所料)

那么为什么即使EmailCandidates 已经返回,响应没有被发送到客户端。怎么知道等待异步SendEmail函数?

【问题讨论】:

  • 快速提问,您对 webconfig 中的这个配置有什么看法?
  • @Zinov 网络配置很大,但自定义错误是 &lt;customErrors mode="Off" /&gt;
  • 查看我的答案

标签: c# asp.net-mvc asynchronous async-await


【解决方案1】:

ASP.NET 提供了一个SynchronizationContext,它会跟踪正在进行的异步操作的数量,并且在它们全部完成之前不会发送结果。请注意,此 SynchronizationContext 已在 ASP.NET Core 中删除。

但是,即使在 ASP.NET 上,您也不应该看到这种行为。在同步方法调用async void 方法的情况下,您应该看到带有消息“此时无法启动异步操作”的InvalidOperationException。对于调用 async void 方法的异步方法(在处理程序返回之前未完成),您应该看到带有消息“异步模块或处理程序已完成,而异步操作仍处于挂起状态。 "

由于这些安全网都没有触发,我怀疑您的 ASP.NET 代码使用的是旧版本的 ASP.NET。这些安全网是在 .NET 4.5 中添加的,您不仅必须将其作为构建目标,而且必须add targetFramework in your web.config

具有以下代码的新 .NET 4.5.2 ASP.NET MVC 应用会立即抛出 InvalidOperationException,正如预期的那样:

public ActionResult About()
{
    ViewBag.Message = "Your application description page.";
    Test();
    return View();
}

private async void Test()
{
    await Task.Delay(20000);
    throw new Exception("Blah");
}

【讨论】:

  • 我们正在使用旧版本的 .NET。事实上,整个问题的出现是因为我们在 web config 中设置了 UseTaskFriendlySynchronizationContext 标志以使用我们刚刚安装的 signalr。然后我们得到了你描述的错误。我不知道 MVC 在返回之前等待所有异步函数。很有趣
  • @Anduril: UseTaskFriendlySynchronizationContext 必须设置为true,否则async/await可能根本无法正常工作。
  • 我们目前不使用任何 async / await 代码(除了这个我不知道的功能,甚至存在于网站的一个很少使用的部分中),所以我们一直到目前为止能够摆脱它。大概,一旦我们将targetFramework 设置为4.5 或更高版本,我们就不需要将UseTaskFriendlySynchronizationContext 设置为true?
  • @Anduril:正确; targetFramework 为 4.5 或更高版本意味着 UseTaskFriendlySynchronizationContext
【解决方案2】:

Async void 方法与“普通”异步方法有点不同。它们有不同的错误处理逻辑。当async Taskasync Task&lt;T&gt; 方法抛出异常时,将捕获该异常并将其放置在Task 对象上。对于 async void 方法,没有 Task 对象,因此 async void 方法引发的任何异常都将直接在调用 async void 方法时处于活动状态的 SynchronizationContext 上引发。可以使用 UnhandledException 事件处理程序观察这些异常。

【讨论】:

    【解决方案3】:

    您的应用程序运行良好,实际上是遵循 MVC 的默认行为。如果您明确提出异常,那么当请求来自应用程序所在的同一台机器(localhost)时会出现这种错误(500),如果您想查看实际用户想要查看的内容,您需要更改默认情况下,它设置为 RemoteOnly。

    如果您转到 FilterConfig。你会看到这行代码

     public static void RegisterGlobalFilters(GlobalFilterCollection filters)
            {
                filters.Add(new HandleErrorAttribute());
            }
    

    尝试将值更改为“On”,您将看到您在该错误页面上结束,因为处理程序错误属性,它为操作提供后处理逻辑,并且当它看到异常已从操作中逃脱时它将显示错误视图而不是黄屏死机。默认情况下,错误视图在您的应用程序中的 View/Shared/Error.cshtml 中

    您的关闭选项

    指定禁用自定义错误。这允许显示 详细的错误。

    参考这里:https://msdn.microsoft.com/en-us/library/h0hfz6fc(v=vs.71).aspx

    如果您只设置远程,那么当您在本地机器上调试网站时,您将继续看到错误,但如果您托管应用程序,最终用户将不会看到该错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-24
      • 1970-01-01
      • 2019-07-13
      • 1970-01-01
      • 1970-01-01
      • 2011-03-04
      相关资源
      最近更新 更多