【问题标题】:ConfigureAwait when not awaiting不等待时配置等待
【发布时间】:2014-03-18 15:00:34
【问题描述】:

我有一个async 方法用于卸载几秒钟的即发即弃的工作,以免减慢我的页面加载速度。这项工作需要一些一般性的设置和整理;我希望(快速)设置在抛出时同步抛出,但我不想强制整理在 ASP 上下文中运行,所以我在等待的位上使用ConfigureAwait

public Task FireAndForget()
{
    DoSetup();
    return FireAndForgetAfterSetup();
}

private async Task FireAndForgetAfterSetup()
{
    await AFewSecondsWorthOfWork().ConfigureAwait(false);
    DoTidyUp();
}

protected void btn_Click(object sender, EventArgs e)
{
    FireAndForget();
}

这看起来很奇怪,因为

  • FireAndForgetAfterSetup 不应该真正关心它是否是从 ASP 上下文中调用的,那么为什么它必须是调用 ConfigureAwait 的那个呢?
  • 如果我改变主意并决定 btn_Click 应该等待 FireAndForget 完成,它是否已经丢弃了 ASP 上下文(?)

如果我理解错了,谁能给我解释一下?

【问题讨论】:

  • 目前你的代码不会同步抛出。返回的任务会出错,但不会抛出异常。我会先解决这个问题:)
  • 您是否知道 ASP.NET 上的“一劳永逸”实际上意味着“我不在乎这段代码是否真的被执行”?您绝对确定您不关心该代码吗?
  • @Jon 很公平,我显然理解的比我想象的要少。 Stephen Fire and forget 可能是错误的措辞,那么,我只想确保至少尝试过代码。
  • @Jon 看起来我因为没有单独的async 和非async 任务返回方法而搞砸了this pattern 的实现。我已经编辑了这个问题,希望能解决这个问题,同时保持其余部分的相关性。
  • 虽然我得到了关于 IIS 如何渴望关闭应用程序池并扼杀我的工作的答案和问题,但我真的很感兴趣拥有一个 库是否是个好主意 async 方法定义与它背后的 UI 相关的行为。我已经接受了 Jon 的 cmets 关于我如何错误地实现我的同步部分的意见,以及其他人关于这是一个坏主意的评论,TBH 我已经回答了我关于ConfigureAwait实际问题阅读更多斯蒂芬的博客文章。

标签: c# asp.net async-await synchronizationcontext azure-webjobs


【解决方案1】:

ASP.NET 同步上下文不允许从请求中启动即发即弃的工作项。运行时会主动监控此类事情,并会尝试生成异常,因为这些代码模式会导致空引用、死锁、AV 和其他问题。

如果您绝对需要在 ASP.NET 中启动即发即弃的工作,请考虑使用WebBackgrounder。它与旨在实现这一点的 ASP.NET 扩展点集成。所以它不会阻止活动请求,但请记住 Stephen 的警告:它永远不能保证执行。如果您需要保证执行,请考虑使用 Service Bus 等可靠性机制。

【讨论】:

  • 我觉得这很奇怪,因为我确实在某种程度上做过这项工作。我触发了我的事件,并获得了页面结果,但在我稍后加载页面之前,我没有观察到“几秒钟的工作价值”的结果。
  • 你基本上只是运气好。绝对不支持这种情况。
  • 嗨@Levi,你能用任何链接或其他东西支持这个吗?挺有意思的。
  • @Levi ASP.NET 确实允许使用 Page.RegisterTaskAsync 方法的即发即弃任务,在 Scott Hanselman 的 post 中进行了描述。 WebBackgrounder 是一种有几个限制的解决方法
  • ASP.NET 确实在乎。有关 ASP.NET 中异步的更多信息,请参阅 vimeo.com/68390507(时间戳 ~37:00)、asp.net/aspnet/overview/web-development-best-practices/… 或我自己的视频 channel9.msdn.com/Events/aspConf/aspConf/Async-in-ASP-NET。 Hanselman 的文章是关于常规异步工作的(not fire-and-forget),Stephen Cleary 的文章顶部有一个巨大的免责声明,上面写着“不推荐使用本博文中的解决方案。”跨度>
【解决方案2】:

如果您的场景是如何在加载期间执行一些(相对)长时间运行的任务,ASP.NET 通过Page.RegisterAsyncTask 方法允许这种场景。 Scott Hansleman 在The Magic of using Asynchronous Methods in ASP.NET 4.5 plus an important gotcha

中描述了如何使用它

本质上,您创建了一个返回 Task 并调用的异步方法:

RegisterAsyncTask(new PageAsyncTask(MyAsyncMethod));

然后调用Page.ExecuteRegisteredAsyncTasks开始执行所有注册的任务。

Scott Hanselman(当然)很好地描述了为什么使用事件处理程序、任务或后台线程是一个坏主意。

这也在“异步页面事件”部分的“What Not to do in ASP.NET, What to do instead”中进行了描述

【讨论】:

  • 我想这有同样的警告,如果 IIS 回收应用程序池,它将如何死亡。它是否允许任务运行的时间超过 ASP 响应完成所需的时间?它是否向 IIS 注册了任务,所以 IIS 会给它 30 秒的时间来完成?
  • @Rawling 不,创建该方法是为了避免这种情况。它实际上通知 IIS 异步任务(这就是它被称为 RegisterXXX 的原因)。不过,我仍在寻找参考资料
  • 我可以相信它会向 IIS 注册任务,所以它知道给他们通知和时间来完成(尽管我仍然很感激它的来源,但它的文档并不完全),但我不会相信 IIS 会给他们所有他们想要完成的时间:p
  • 不,不会。有一个可配置的异步超时,默认为 45 秒。您还可以向 PageAsyncTask 提供回调以处理超时
【解决方案3】:

我不确定他为什么不发布,但我的确切两个问题在this blog post by Stephen Cleary 中得到了回答:

此示例需要注意的重要一点是异步方法调用的每个“级别”都有自己的上下文。 DownloadFileButton_Click 在 UI 上下文中启动,并调用 DownloadFileAsync。 DownloadFileAsync 也在 UI 上下文中启动,但随后通过调用 ConfigureAwait(false) 退出其上下文。 DownloadFileAsync 的其余部分在线程池上下文中运行。但是,当 DownloadFileAsync 完成并且 DownloadFileButton_Click 恢复时,它会在 UI 上下文中恢复。

一个好的经验法则是使用 ConfigureAwait(false),除非你知道你确实需要上下文。

  • 在回答我的第一个要点时,是的,可以(鼓励!)在知道它们不会使用上下文的库方法中使用 ConfigureAwait(false),因为...
  • ...在回答我的第二个要点时,即使库 async 方法已经丢弃了 UI 上下文,调用方法仍然有自己的副本。因此,调用方法可以await 库方法并在 UI 上下文中恢复...但是当这种情况发生时它会死锁。

【讨论】:

    猜你喜欢
    • 2018-12-09
    • 1970-01-01
    • 1970-01-01
    • 2018-05-04
    • 2020-05-09
    • 2021-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多