异步等待功能使得编写非阻塞代码更加优雅。但是,虽然非阻塞,但在异步函数内执行的工作仍然是不平凡的。何时同时运行异步功能?
在编写异步代码时,我发现编写遵循模式“沿着兔子洞一路向下”的代码是很自然的,可以这么说,调用树中的所有方法都标记为异步,并且使用的API是异步;但即使在非阻塞的情况下,执行的代码也会占用相当多的上下文线程时间。
如何以及何时异步运行同步异步方法?是否应该在调用树中创建更高或更低的新任务时出错?有没有这种“优化”的最佳做法?
异步等待功能使得编写非阻塞代码更加优雅。但是,虽然非阻塞,但在异步函数内执行的工作仍然是不平凡的。何时同时运行异步功能?
在编写异步代码时,我发现编写遵循模式“沿着兔子洞一路向下”的代码是很自然的,可以这么说,调用树中的所有方法都标记为异步,并且使用的API是异步;但即使在非阻塞的情况下,执行的代码也会占用相当多的上下文线程时间。
如何以及何时异步运行同步异步方法?是否应该在调用树中创建更高或更低的新任务时出错?有没有这种“优化”的最佳做法?
我一直在使用async
生产了几年。我推荐几个核心“最佳实践”:
async
code。 “一路下来”使用async
。 (推论:更喜欢async Task
到async void
除非你有使用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
添加ConfigureAwait(false)
每await
:
async Task<string> B()
{
var rawString = await SomeOtherStuff().ConfigureAwait(false);
var result = DoProcessingOnRawString(rawString);
return result;
}
现在,当后B
简历await
荷兰国际集团SomeOtherStuff
(假设它实际上确实有await
),它会恢复一个线程池线程,而不是的UI上下文。当B
完成时,即使它正在线程池上运行,A
将在UI上下文中恢复。
您不能将ConfigureAwait(false)
添加到A
,因为A
取决于UI上下文。
您还可以选择将任务明确排队到线程池(await Task.Run(..)
),如果您有特定的CPU密集型功能,则您可以应该。但是,如果您的表现受到“成千上万的剪纸”的影响,您可以使用ConfigureAwait(false)
将大量的“家务”卸载到线程池中。
你可能会发现我的intro post helpful(它进入更多的“为什么”),而async
FAQ也有很多很好的参考。
异步等待does not actually use threads in the current .NET process-space。它专为阻塞IO和网络操作而设计,如数据库调用,Web请求,某些文件IO。
我无法感知C#中的优势是什么,您称之为兔洞技术。这样做只会掩盖代码,不必要地将潜在的高CPU代码耦合到您的IO代码。
直接回答你的问题,我只会用异步等待对上述IO /网络方案,就在那里,你正在做的阻塞操作点,以及任何被CPU绑定,我会用穿线技术来充分利用可用的CPU内核。没有必要混合这两个问题。
非常感谢你,这是一个顶尖的答案,真的有助于澄清这个问题! –
“几年”? – spender
异步CTP,回来的路......我觉得在这一点上差不多2年。我一直在做十几年的异步编程(http://nitoprograms.blogspot.com/2012/09/an-async-horror-story.html),所以我是一个早期的早期采用者'async'。 –