何时同时运行异步功能?

And*_*lon 1 c# concurrency async-await

async-await功能使编写非阻塞代码变得优雅.但是,在非阻塞的情况下,在异步函数中执行的工作仍然可以是非平凡的.

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

您是如何以及何时在异步上同时运行异步方法的?如果在调用树中创建了更高或更低的新任务,是否应该错误?这种"优化"是否有最佳实践?

Ste*_*ary 6

我已经async在生产中使用了几年.我推荐一些核心"最佳实践":

  1. 不要阻止async代码.使用async"一直向下".(推论:除非你必须使用async Task,async void否则更喜欢).async void
  2. 使用ConfigureAwait(false)尽可能在您的"库"的方法.

你已经找到了" async一直向下"的部分,而你正处于ConfigureAwait(false)有用的地步.

假设你有一个async方法A调用另一个async方法B.A使用结果更新UI B,但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;
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,我会调用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;
}
Run Code Online (Sandbox Code Playgroud)

现在,当B恢复后,await荷兰国际集团SomeOtherStuff(假设它实际上确实有await),它将恢复对线程池线程,而不是UI上下文.当B完成后,即使它的线程池运行时,A将恢复在UI方面.

您无法添加ConfigureAwait(false),A因为A取决于UI上下文.

您还可以选择将任务显式排队到线程池(await Task.Run(..)),如果您具有特定的CPU密集型功能,则应该执行此操作.但是如果你的表现遭受"成千上万的剪纸",你可以ConfigureAwait(false)用来将大量的async"管家" 卸载到线程池中.

您可能会发现我的介绍帖子很有帮助(它更多地涉及"为什么"),而asyncFAQ也有很多很棒的参考资料.