当前位置: 首页 > 知识库问答 >
问题:

为什么我需要在所有传递闭包中使用ConfigureAwait(false)?

全彬
2023-03-14

我正在学习异步/等待,在阅读本文之后,不要阻塞异步代码

这是异步/等待适合于 IO 和 CPU 绑定的方法

我注意到@Stephen Cleary文章中的一个提示。

使用ConfigureAwait(false)避免死锁是一种危险的做法。您必须对阻塞代码调用的所有方法(包括所有第三方和第二方代码)的传递闭包中的每个等待使用ConfigureAwait(false)。使用ConfigureAwait(false)避免死锁充其量只是一种黑客行为)。

它再次出现在我上面所附的帖子代码中。

public async Task<HtmlDocument> LoadPage(Uri address)
{
    using (var httpResponse = await new HttpClient().GetAsync(address)
        .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
    using (var responseContent = httpResponse.Content)
    using (var contentStream = await responseContent.ReadAsStreamAsync()
        .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
        return LoadHtmlDocument(contentStream); //CPU-bound
}

据我所知,当我们使用配置等待(false)时,其余的异步方法将在线程池中运行。为什么我们需要将它添加到传递闭包中的每个等待中?我自己只是认为这是我所知道的正确版本。

public async Task<HtmlDocument> LoadPage(Uri address)
{
    using (var httpResponse = await new HttpClient().GetAsync(address)
        .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
    using (var responseContent = httpResponse.Content)
    using (var contentStream = await responseContent.ReadAsStreamAsync()) //IO-bound
        return LoadHtmlDocument(contentStream); //CPU-bound
}

这意味着在使用块时第二次使用配置等待(false)是无用的。请告诉我正确的方法。提前谢谢。

共有1个答案

终子昂
2023-03-14

据我所知,当我们使用 ConfigureAwait(false) 时,异步方法的其余部分将在线程池中运行。

很接近,但有一个重要的警告,你错过了。当您使用<code>ConfigureAwait(false)</code>等待任务后恢复时,您将在任意线程上恢复。注意“恢复时”这几个字

让我给你看点东西:

public async Task<string> GetValueAsync()
{
    return "Cached Value";
}

public async Task Example1()
{
    await this.GetValueAsync().ConfigureAwait(false);
}

考虑示例1中的等待。尽管您正在等待异步方法,但该方法实际上并不执行任何异步工作。如果<code>异步

跟你预想的不太一样,对吧?然而,这并不罕见。许多< code>async方法可能包含不需要调用方挂起的快速路径。缓存资源的可用性就是一个很好的例子(谢谢,@jakub-dąbek!),但是还有很多其他原因导致< code>async方法可能提前退出。我们经常在方法的开始检查各种条件,看看我们是否可以避免做不必要的工作,< code>async方法也不例外。

让我们看另一个示例,这次来自WPF应用程序:

async Task DoSomethingBenignAsync()
{
    await Task.Yield();
}

Task DoSomethingUnexpectedAsync()
{
    var tcs = new TaskCompletionSource<string>();
    Dispatcher.BeginInvoke(Action(() => tcs.SetResult("Done!")));
    return tcs.Task;
}

async Task Example2()
{
    await DoSomethingBenignAsync().ConfigureAwait(false);
    await DoSomethingUnexpectedAsync();
}

看一看示例2。我们<code>等待<code>的第一个方法总是异步运行。当我们点击第二个<code>等待<code>时,我们知道我们正在线程池线程上运行,所以第二次调用时不需要<code>配置等待(false)Async并返回任务,但我们的第二个方法不是使用异步等待编写的。相反,它执行自己的调度,并使用TaskCompletionSource传递结果。当您从<code>等待<code>恢复时,您可能会<sup>[1]

这里关键的一点是,你通常不知道一个“可行的”方法到底是做什么的。不管有没有< code > configurewait ,您都可能会运行到某个意想不到的地方。这可能发生在< code>async调用堆栈的任何级别,因此避免无意中获得单线程上下文所有权的最可靠方法是对每个< code>await使用< code > configurewait(false),即贯穿传递闭包。

当然,有些时候你可能想在当前的背景下继续工作,这没关系。这就是为什么它是默认行为的表面原因。但是如果你真的不需要它,那么我推荐默认使用< code > configurewait(false)。对于库代码来说尤其如此。库代码可以从任何地方被调用,所以最好坚持最小惊奇的原则。这意味着当您不需要时,不要将其他线程锁定在调用者的上下文之外。即使您在库代码中处处使用< code > configurewait(false),调用者仍然可以选择在他们的原始上下文上继续,如果这是他们想要的。

[1]此行为可能因框架和编译器版本而异。

 类似资料:
  • 在模块声明中,requires和requires可传递模块语句之间有什么区别 例如:

  • 问题内容: 我有一个将对象保存到数据库的EJB。在我看到的一个示例中,一旦保存了此数据(EntityManager.persist),就会调用EntityManager.flush();。为什么我需要这样做?我要保存的对象未附加,以后在该方法中也不会使用。实际上,一旦保存,该方法就会返回,并且我希望资源会被释放。(示例代码也在remove调用上执行此操作。) 问题答案: 调用将强制数据立即被持久保

  • 问题内容: 大多数情况下,我看到的finally块仅用于 我的问题是,如果f的范围以封闭块结尾,为什么我们需要在finally中将其关闭? 问题答案: 因为垃圾回收与资源清理 不是 一回事。 例如,如果您有一个超出范围的JDBC连接对象,则没有信号发送到数据库服务器以指示不再需要打开的游标和连接。没有这些消息,您最终将耗尽可用的游标和连接数。 与文件句柄和任何其他资源相同。自己清理后。

  • 我在互联网上找到了例子,但这并没有给我充分的理解。使用WebFlux时的标准CRUD。 路由器: 处理程序: 我是对的还是这个说法错了?

  • 我正在阅读有关java中的同步概念的信息,并遇到了同步语句。 我想知道,为什么我们向它传递参数,尽管它看起来像静态块(这只是一个例子),并且传递的参数没有指定任何数据类型。 例: 如果有人知道,请解释。

  • 问题内容: 我开始使用RxJS,但我不明白为什么在此示例中我们需要使用类似or 的函数;数组的数组在哪里? 如果有人可以直观地解释正在发生的事情,那将非常有帮助。 问题答案: 当您有一个Observable的结果是更多Observable时,可以使用flatMap。 如果您有一个由另一个可观察对象产生的可观察对象,则您不能直接过滤,缩小或映射它,因为您有一个可观察对象而不是数据。如果您生成一个可观