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

vietvoquoc:

我正在学习异步/等待,并且在阅读了这篇文章后,不要在异步代码上阻止

是async / await适用于同时受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
}

据我所知,当我们使用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()) //IO-bound
        return LoadHtmlDocument(contentStream); //CPU-bound
}

这意味着在using块中第二次使用ConfigureAwait(false)是没有用的。请告诉我正确的方法。提前致谢。

Mike Strobel:

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

接近,但是有一个重要的警告提示您缺少。当您等待带有的任务后继续执行时ConfigureAwait(false),您将在任意线程上继续进行。注意“恢复时”一词。

让我给你看一些东西:

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

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

考虑awaitExample1尽管您正在等待一个async方法,但是该方法实际上并不执行任何异步工作。如果某个async方法没有await任何用处,它将同步执行,并且等待者将永远不会恢复,因为它从来没有挂起过。如本例所示,对to的调用ConfigureAwait(false)可能是多余的:它们可能根本没有作用。在此示例中,无论您输入什么上下文,都Example1将在后面await

不太符合您的期望,对吧?但是,这并非完全不寻常。许多async方法可能包含不需要调用者挂起的快速路径。缓存资源的可用性就是一个很好的例子(感谢@jakub-dąbek!),但是还有很多其他原因async可能导致方法过早保释。我们经常在方法开始时检查各种条件,以查看是否可以避免进行不必要的工作,并且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();
}

看一看Example2我们await 总是异步运行的第一种方法当我们到达第二await个线程时,我们知道我们正在线程池线程上运行,因此ConfigureAwait(false)第二个调用不再需要,对吗?错误。尽管有Async名字并返回a Task,但我们的第二种方法并未使用asyncand 编写await相反,它执行自己的调度并使用TaskCompletionSource来传达结果。从中恢复时await[1]可能最终在提供结果的任何线程上运行,在本例中为WPF的调度程序线程。哎呀

这里的关键是外卖,你往往不知道确切的“awaitable”的方法做了什么。随着还是没有ConfigureAwait,你可能最终运行在某处的意外。这可以在async调用堆栈的任何级别上发生,因此避免无意中获得单线程上下文所有权的最可靠方法是ConfigureAwait(false)每一个 await(即在整个传递闭包中)一起使用。

当然,有时您可能希望在当前上下文中恢复,这很好。表面上,这就是为什么它是默认行为。但是,如果您不是真正需要它,那么我建议ConfigureAwait(false)默认使用对于库代码尤其如此。可以从任何地方调用库代码,因此最好遵循最少惊喜的原则。这意味着在不需要时,不要将其他线程锁定在调用者的上下文之外。即使您ConfigureAwait(false)在库代码中的任何地方使用代码,只要他们想要的,调用者仍然可以选择在原始上下文中恢复

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么我们需要在Java中使用移位运算符?

角度2:要在所有组件中使用的功能

为什么我永远不需要在Redux中使用subscription?

为什么我们需要闭包内的函数/方法使用weakSelf?

当闭包(似乎)永远无法访问时,为什么要在闭包的类型注释中使用“内部参数标签”?

为什么在闭包参数中使用“ &&”?

为什么在Jenkinsfile中的DSL闭包中使用`env`或`param`时需要关键字`this`

为什么我需要在node-postgres中使用async / await两次

为什么此闭包也需要在外部函数上返回?

为什么总是建议在每条带有await的行上编写ConfigureAwait(false),我真的需要吗?

为什么我们需要在Rust中指定所有依赖项(包括传递对象)?

为什么在转让所有权后可以使用非捕获闭包?

为什么需要在c#中使用所有属性而不是公共实例变量?

为什么要在代码中考虑使用JavaScript闭包?

我是否需要在所有子页面上重复所有类别?

为什么我们需要在图灵的暂停证明中使用否定部分?

为什么我需要在Android中使用super()?

我是否需要在下拉菜单中使用Jquery,以便它在所有设备上均可使用?

为什么我们需要在C ++头文件中使用“ #if defined Identifier”?

为什么这种闭包没有我期望的论点?

为什么我必须在 ES6 的地图闭包中使用 return ?

为什么我需要在 shareReplay 中使用管道?

为什么我需要在以下场景中使用 ReactJS state 在 AJAX 之后更新 UI

在将闭包传递给 gradle 扩展时,为什么闭包的所有者不是主要的 Projects 对象?

为什么我需要在实现接口的类中实现接口的所有方法?

为什么我需要在代码中使用 `display: flex` 来实现纵横比技术?

为什么我有时需要在列表中使用 key()?

为什么我需要在 Django 中使用 SlugField?

为什么我有时需要在 proc 作为 arg 传递时使用 & 前缀?