C# 嵌套异步/等待不';我看起来不太舒服

C# 嵌套异步/等待不';我看起来不太舒服,c#,asynchronous,async-await,tpl-dataflow,C#,Asynchronous,Async Await,Tpl Dataflow,我有以下(简化)代码: 公共异步任务GetData(DomainObject DomainObject,int depth) { //这个异步操作非常快,通常有五个。 IEnumerable tierOnes=await domainObject.GetTierOnesAsync(); var tierOneTasks=tierOnes.Select(异步tierOne=> { //这个异步操作非常快,通常有三个。 IEnumerable tierTwos=wait tierOne.GetTie

我有以下(简化)代码:

公共异步任务GetData(DomainObject DomainObject,int depth) { //这个异步操作非常快,通常有五个。 IEnumerable tierOnes=await domainObject.GetTierOnesAsync(); var tierOneTasks=tierOnes.Select(异步tierOne=> { //这个异步操作非常快,通常有三个。 IEnumerable tierTwos=wait tierOne.GetTierTwosAsync(); 如果(深度) { //这种异步操作通常很快,并且通常大于等于100。 IEnumerable TierTrees=等待tierTwo.GetTierThreesAsync(); 如果(深度) { //这个异步操作很慢,通常有..50? 等待TierTree.GetTierFoursAsync(); }); 等待Task.WhenAll(tierThreeTasks.ToArray()); }); wait Task.WhenAll(tierTwoTasks.ToArray()); }); 等待任务.WhenAll(tierOneTasks.ToArray()); } 根据我所看到的,它的伸缩性似乎不是很好。所有的
Async
操作都是“真正的异步”操作,这意味着它们都是I/O


对于这种情况,我是否错误地使用了Async/Wait?根据我目前的观察,它并没有达到我的预期。TPL数据流是我的解决方案吗?

对于对
GetData
的单个调用,嵌套的异步/等待调用不会引入任何并发性。检索所有tierOne,然后检索tierOne-#1的所有tierTwo,然后检索tierTwo-#1的所有TierTrees,依此类推,所有这些都按顺序运行(尽管GetTier*异步方法中可能存在一些并发性)


如果您想要并发请求,那么TPL数据流确实是一个更好的解决方案。

“它似乎扩展得很好”这是一个输入错误,您需要在其中添加
而不是
?如果是这样的话,以什么方式扩展,您希望它更快地完成,还是不给系统增加那么多负载?您是如何测试伸缩性的?您使用了大量的
IEnumerables
作为异步返回值。您确定延迟执行不会干扰您假定的并行化吗?异步不会使它更快,事实上,由于开销的原因,它通常会使它比同步版本的相同代码慢一点,它所做的是在性能下降之前允许更高的负载。@Cameron:由于您的方法都在执行I/O(可能是针对同一台服务器),请仔细检查您的
ServicePointManager.DefaultConnectionLimit
设置。或者在启动时将其设置为
int.MaxValue
,看看问题是否仍然存在。这缺少一个非官方标准,即客户端(浏览器、应用程序等)不应向同一域发出两个以上的HTTP请求。一个更好的实现是使用ActionBlock,例如10个并发任务,这样即使有100个URL要点击,也可以控制并发请求的数量。更好的是,每层可以有一个具有不同DOP设置的块,将其结果提供给下一个tierI。我不认为是这样。一个级别中的所有任务都是(正确地)独立启动的(因为Select方法,而WhenAll方法稍后会“等待”)。并发性不是由async/await引入的,而是使用LINQ创建任务的事实。我能想到的唯一一件事是,他只是耗尽了线程,这发生在你处理嵌套异步操作的情况下(这几乎就像一个递归,显然在几个级别之后停止——但不是在耗尽线程池之前)。@Miklós正如OP所写,他的
GetAsync
方法是真正的异步I/O,因此,它们在运行时不会阻塞任何线程。我从
DispatchersSynchronizationContext
运行
GetData
(使用
Task.Delay
调用),没有并发性,因此线程池无法限制性能。从空同步上下文运行
GetData
确实会引入并发性,但是如果到服务器的I/O比发送/接收代码慢(如OP所述),线程池中的瓶颈只占总等待时间的很小一部分。他的瓶颈可能在其他地方(如斯蒂芬·克利里所说)。
public async Task GetData(DomainObject domainObject, int depth)
{
  // This async operation is really quick, and there's usually like five.
  IEnumerable<TierOne> tierOnes = await domainObject.GetTierOnesAsync();

  var tierOneTasks = tierOnes.Select(async tierOne => 
  {
    // This async operation is really quick and there's usually like three.
    IEnumerable<TierTwo> tierTwos = await tierOne.GetTierTwosAsync();

    if (depth <= TierTwoDepth)
      return;

    var tierTwoTasks = tierTwos.Select(async tierTwo => 
    {
      // This async operation is usually fast, and there's usually >= 100.
      IEnumerable<TierThree> tierThrees = await tierTwo.GetTierThreesAsync();

      if (depth <= TierThreeDepth)
        return;

      var tierThreeTasks = tierThrees.Select(async tierThree => 
      {
        // This async operation is SLOW, and there's usually.. 50?
        await tierThree.GetTierFoursAsync();
      });

      await Task.WhenAll(tierThreeTasks.ToArray());
    });

    await Task.WhenAll(tierTwoTasks.ToArray());
  });

  await Task.WhenAll(tierOneTasks.ToArray());
}