async-await功能使编写非阻塞代码变得优雅。但是,在非阻塞的情况下,在异步函数中执行的工作仍然可以是非平凡的。
在编写异步代码时,我发现编写遵循“兔子洞一直”模式的代码是很自然的,可以这么说,调用树中的所有方法都标记为异步,并且使用的API是异步的;但即使在非阻塞的情况下,执行的代码也会占用相当多的上下文线程的时间。
如何以及何时决定在异步上同时运行异步方法?如果在调用树中创建了更高或更低的新任务,是否应该错误?这种“优化”是否有最佳实践?
答案 0 :(得分:6)
我已经在生产中使用async
几年了。我推荐一些核心“最佳实践”:
async
code。完全使用async
“。 (推论:首选async Task
至async void
,除非您 使用async void
。)ConfigureAwait(false)
。您已经找到了“async
一直向下”的部分,而您正处于ConfigureAwait(false)
变得有用的位置。
假设您使用async
方法A
调用另一个async
方法B
。 A
使用B
的结果更新用户界面,但B
不依赖于用户界面。所以我们有:
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
导致响应问题。
所以,为ConfigureAwait(false)
中的每个await
添加B
:
async Task<string> B()
{
var rawString = await SomeOtherStuff().ConfigureAwait(false);
var result = DoProcessingOnRawString(rawString);
return result;
}
现在,当B
await
SomeOtherStuff
之后恢复await
时(假设它确实需要B
),它将在线程池线程而不是UI上恢复上下文。当A
完成时,即使它在线程池上运行,ConfigureAwait(false)
也将在UI上下文中恢复。
您无法将A
添加到A
,因为await Task.Run(..)
取决于UI上下文。
您还可以选择将任务显式排队到线程池(ConfigureAwait(false)
),如果您具有特定的CPU密集型功能,应该执行此操作。但是如果你的表现受到“数千次剪纸”的影响,你可以使用async
将大量{{1}}“内务管理”卸载到线程池中。
你可能会发现我的intro post helpful(它更多地涉及“为什么”),async
FAQ也有很多很棒的参考资料。
答案 1 :(得分:0)
Async-await does not actually use threads in the current .NET process-space。它旨在“阻止”IO和网络操作,如数据库调用,Web请求,一些文件IO。
我无法理解C#对你所谓的兔子洞技术有什么好处。这样做只会掩盖代码,并且不必要地将潜在的高CPU代码与IO代码耦合在一起。
要直接回答你的问题,我只会对前面提到的IO /网络场景使用async-await,在你正在进行阻塞操作时使用,对于任何受CPU限制的事情都是如此我会使用线程技术来充分利用可用的CPU核心。无需混淆这两个问题。