1

异步等待功能使编写非阻塞代码变得优雅。但是,虽然是非阻塞的,但在异步函数中执行的工作仍然很重要。

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

您如何以及何时决定在异步之上同时运行支持异步的方法?是否应该在调用树中创建更高或更低的新任务?这种“优化”有什么最佳实践吗?

4

2 回答 2

6

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

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

您已经弄清楚了“async一直向下”的部分,并且您正处于ConfigureAwait(false)变得有用的地步。

假设您有一个调用另一个async方法的方法。使用 的结果更新 UI ,但不依赖于 UI。所以我们有:AasyncBABB

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)每个awaitin 中添加一个B

async Task<string> B()
{
  var rawString = await SomeOtherStuff().ConfigureAwait(false);
  var result = DoProcessingOnRawString(rawString);
  return result;
}

现在,当在ingB之后恢复时(假设它确实必须),它将在线程池线程而不是 UI 上下文上恢复。完成后,即使它在线程池上运行,也会在 UI 上下文中恢复。awaitSomeOtherStuffawaitBA

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

您还可以选择将任务显式排队到线程池 ( await Task.Run(..)),如果您有特定的 CPU 密集型功能,您应该这样做。但是,如果您的性能受到“数千次剪纸”的影响,您可以使用ConfigureAwait(false)将大量async“家务”卸载到线程池中。

您可能会发现我的介绍性帖子很有帮助(它涉及更多“为什么”),并且async常见问题解答也有很多很好的参考资料。

于 2012-09-18T18:44:25.317 回答
0

Async-await实际上并不使用当前 .NET 进程空间中的线程。它是为“阻塞” IO 和网络操作而设计的,例如数据库调用、Web 请求、一些文件 IO。

我无法理解 C# 对你所说的兔子洞技术有什么好处。这样做只会使代码变得模糊,并且不必要地将潜在的高 CPU 代码与 IO 代码耦合。

为了直接回答你的问题,我只会在前面提到的 IO/网络场景中使用 async-await,就在你正在执行阻塞操作的地方,对于任何受 CPU 限制的事情,我会使用线程技术来充分利用可用的 CPU 内核数。无需将这两个问题混为一谈。

于 2012-09-18T18:06:55.713 回答