几年来,我一直在生产中使用async。我推荐几个核心“最佳实践”:
-
Don't block on
async code。使用async“一直向下”。 (推论:比起async void,更喜欢async Task,除非你必须使用async void)。
- 在您的“库”方法中尽可能使用
ConfigureAwait(false)。
您已经弄清楚了“async 一直向下”部分,并且您正处于 ConfigureAwait(false) 变得有用的地步。
假设您有一个async 方法A 调用另一个async 方法B。 A 使用 B 的结果更新 UI,但 B 不依赖于 UI。所以我们有:
async Task A()
{
var result = await B();
myUIElement.Text = result;
}
async Task<string> B()
{
var rawString = await SomeOtherStuff();
var result = DoProcessingOnRawString(rawString);
return result;
}
在本例中,我将 B 称为“库”方法,因为它实际上并不需要在 UI 上下文中运行。现在,B确实在 UI 线程中运行,所以 DoProcessingOnRawString 会导致响应问题。
所以,给B 中的每个await 添加一个ConfigureAwait(false):
async Task<string> B()
{
var rawString = await SomeOtherStuff().ConfigureAwait(false);
var result = DoProcessingOnRawString(rawString);
return result;
}
现在,当B 在awaiting SomeOtherStuff 之后恢复时(假设它确实必须await),它将在线程池线程而不是 UI 上下文上恢复。当B 完成时,即使它在线程池上运行,A 也会在 UI 上下文中恢复。
您不能将ConfigureAwait(false) 添加到A,因为A 取决于UI 上下文。
您还可以选择将任务显式排队到线程池 (await Task.Run(..)),如果您有特定的 CPU 密集型功能,您应该这样做。但是如果你的性能受到了“千刀万剐”的困扰,你可以使用ConfigureAwait(false)将大量的async“家务”卸载到线程池中。
您可以找到我的intro post helpful(它涉及更多“为什么”),async FAQ 也有很多很好的参考。