2012-09-18 160 views
1

异步等待功能使得编写非阻塞代码更加优雅。但是,虽然非阻塞,但在异步函数内执行的工作仍然是不平凡的。何时同时运行异步功能?

在编写异步代码时,我发现编写遵循模式“沿着兔子洞一路向下”的代码是很自然的,可以这么说,调用树中的所有方法都标记为异步,并且使用的API是异步;但即使在非阻塞的情况下,执行的代码也会占用相当多的上下文线程时间。

如何以及何时异步运行同步异步方法?是否应该在调用树中创建更高或更低的新任务时出错?有没有这种“优化”的最佳做法?

回答

6

我一直在使用async生产了几年。我推荐几个核心“最佳实践”:

  1. Don't block on async code。 “一路下来”使用async。 (推论:更喜欢async Taskasync void除非你使用async void)。
  2. 尽可能在您的“库”方法中使用ConfigureAwait(false)

您已经计算出“async一路下降”的部分,并且您正处于ConfigureAwait(false)变得有用的程度。

假设您有一个async方法A,该方法调用另一个async方法BAB的结果更新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也有很多很好的参考。

+0

非常感谢你,这是一个顶尖的答案,真的有助于澄清这个问题! –

+0

“几年”? – spender

+0

异步CTP,回来的路......我觉得在这一点上差不多2年。我一直在做十几年的异步编程(http://nitoprograms.blogspot.com/2012/09/an-async-horror-story.html),所以我是一个早期的早期采用者'async'。 –

0

异步等待does not actually use threads in the current .NET process-space。它专为阻塞IO和网络操作而设计,如数据库调用,Web请求,某些文件IO。

我无法感知C#中的优势是什么,您称之为兔洞技术。这样做只会掩盖代码,不必要地将潜在的高CPU代码耦合到您的IO代码。

直接回答你的问题,我只会用异步等待对上述IO /网络方案,就在那里,你正在做的阻塞操作点,以及任何被CPU绑定,我会用穿线技术来充分利用可用的CPU内核。没有必要混合这两个问题。