何时同时运行异步功能?

时间:2012-09-18 17:29:43

标签: c# concurrency async-await

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

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

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

2 个答案:

答案 0 :(得分:6)

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

  1. Don't block on async code。完全使用async“。 (推论:首选async Taskasync void,除非您 使用async void。)
  2. 尽可能在“库”方法中使用ConfigureAwait(false)
  3. 您已经找到了“async一直向下”的部分,而您正处于ConfigureAwait(false)变得有用的位置。

    假设您使用async方法A调用另一个async方法BA使用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核心。无需混淆这两个问题。