我正在学习异步/等待,并且在阅读了这篇文章后,不要在异步代码上阻止
这是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)是没有用的。请告诉我正确的方法。提前致谢。
据我所知,当我们使用
ConfigureAwait(false)
其余异步方法时,将在线程池中运行。
接近,但是有一个重要的警告提示您缺少。当您等待带有的任务后继续执行时ConfigureAwait(false)
,您将在任意线程上继续进行。注意“恢复时”一词。
让我给你看一些东西:
public async Task<string> GetValueAsync()
{
return "Cached Value";
}
public async Task Example1()
{
await this.GetValueAsync().ConfigureAwait(false);
}
考虑await
在Example1
。尽管您正在等待一个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
,但我们的第二种方法并未使用async
and 编写await
。相反,它执行自己的调度并使用TaskCompletionSource
来传达结果。从中恢复时await
,[1]可能最终在提供结果的任何线程上运行,在本例中为WPF的调度程序线程。哎呀
这里的关键是外卖,你往往不知道确切的“awaitable”的方法做了什么。随着还是没有ConfigureAwait
,你可能最终运行在某处的意外。这可以在async
调用堆栈的任何级别上发生,因此避免无意中获得单线程上下文所有权的最可靠方法是ConfigureAwait(false)
与每一个 await
(即在整个传递闭包中)一起使用。
当然,有时您可能希望在当前上下文中恢复,这很好。表面上,这就是为什么它是默认行为。但是,如果您不是真正需要它,那么我建议ConfigureAwait(false)
默认使用。对于库代码尤其如此。可以从任何地方调用库代码,因此最好遵循最少惊喜的原则。这意味着在不需要时,不要将其他线程锁定在调用者的上下文之外。即使您ConfigureAwait(false)
在库代码中的任何地方使用代码,只要他们想要的,调用者仍然可以选择在其原始上下文中恢复。
[1]此行为可能会因框架和编译器版本而异。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句