【问题标题】:Adding async value during the interation c#在交互 c# 期间添加异步值
【发布时间】:2020-09-29 04:16:36
【问题描述】:

我的控制器调用了一个方法,我正在尝试在迭代期间添加新值

public async Task<MyResult> GetItems(int itemId) {
try
{
    //returns a lists of items
    var resList = await _unit.repository.GetAll(x => x.ItemId.Equals(itemId));

    if(resList.Count() == 0)
        throw new Exception(err.Message);

    //Here i need to list the sub-items, but the method returns async
    resList.ToList().ForEach(async res => {
        switch (res.Type)
        {
            case Itemtype.None :
                res.SubItems = await _unit.repository.GetAll(y => y.ItemId(res.ItemId));
            break;
            case Itemtype.Low :
                //get from another place
            break;
        } 
    });

    return new MyResult { 
        res = resList
    };
}
catch (Exception ex)
{
    throw ex;
}

}

显示了我的项目,但没有子项目

【问题讨论】:

  • 事实上,您正在迭代一个临时列表(由ToList() 创建)并将子项存储在那里,而不是原始的reslist。如果将 ToList().ForEach 替换为直接更新 resList 的简单 foreach 循环,则可以解决此问题。
  • 什么是ForEach方法?
  • @JohnWu 但ToList 没有创建新的res,它正在修改现有的。我认为切换到foreach 会有所帮助,但因为代码实际上将await 用于结果,而不仅仅是创建resList.Count() 的任务并返回。
  • List.ForEach + async = async void。查看 Gabriel Luci 对这个问题的回答:Async void lambda expressions

标签: c# asp.net .net asp.net-mvc async-await


【解决方案1】:

请注意,在foreach 循环内使用await 将暂停迭代,直到Task 完成。

如果您有大量项目,这可能会导致性能很差。

允许任务同时运行:

var tasks = resList.Select(async res =>
{
    switch (res.Type)
    {
        case Itemtype.None :
            res.SubItems = await _unit.repository.GetAll(y => y.ItemId(res.ItemId));
        break;
        case Itemtype.Low :
            //get from another place
        break;
    } 
});

然后使用Task.WhenAll 让它们全部完成:

await Task.WhenAll(tasks);

Enumerable.SelectList&lt;T&gt;.ForEach 的工作方式不同,因为它从提供的委托(在本例中为 Task)返回。

【讨论】:

    【解决方案2】:

    重要的是要意识到await 关键字在作用于不完整的Task 时将返回。通常它会返回自己的不完整的Task,调用者可以使用它来等待它完成,但如果方法签名是void,它不会返回任何内容。

    无论ForEach() 是什么,它很可能不接受返回Task 的委托,这意味着您的匿名方法(async res =&gt; { ... })具有void 返回类型。

    这意味着只要GetAll() 中的网络请求发送,您的匿名方法就会返回。因为ForEach() 没有等待它,所以它继续进行下一次迭代。当ForEach() 完成所有事情时,它所做的只是发送请求,但没有等待它们。因此,当您收到 return 声明时,您无法确定是否已完成任何操作。

    .ForEach() 调用替换为常规的 foreach,这样您就不会在 void 委托中执行该工作。然后您应该会看到它的行为更像您的预期。更好的是,使用Johnathan's answer 启动循环内的所有任务,然后等待它们。

    微软在Asynchronous programming with async and await 上有一系列写得很好的文章,我认为你会从阅读中受益。您可以在该页面左侧的目录中找到该系列的其余部分。

    【讨论】:

      【解决方案3】:

      resList.ToList().ForEach(...)切换到普通foreach

      foreach (var res in resList)
      {
          switch (res.Type)
          {
              case Itemtype.None:
                  res.SubItems = await _unit.repository.GetAll(y => y.ItemId(res.ItemId));
                  break;
              case Itemtype.Low:
                  //get from another place
                  break;
          }
      }
      

      看来您正在使用List&lt;T&gt;.ForEach。它接受Action&lt;T&gt; 并且由于您传递给它的操作是异步的,因此您当前的代码将只创建任务的resList.Count() 并继续返回语句而不实际等待它们。简单的复制器可能如下所示:

      class MyClass { public int i; }
      
      var col = new[] { new MyClass { i = 1 }, new MyClass { i = 2 } };
      col.ToList().ForEach(async i => { await Task.Delay(1); i.i *= 10; });
      //col.ToList().ForEach(i => { i.i *= 10; });
      
      Console.WriteLine(string.Join(" ", col.Select(mc => mc.i))); // will print "1 2"
      

      【讨论】:

      • 我认为 async 应该在 foreach 前面
      • @MikeCheel 似乎 OP 正在使用 List&lt;T&gt;.ForEach ,它返回 void
      • 我的意思是在你的 foreach 面前。 for 循环中的异步可能会导致问题(我认为在 7.3(?)之前)c# 中,所以我通常使用 Task.WaitAll (或类似的)。 async foreach 解决了这个问题(在 c# 8 中)? medium.com/@t.masonbarneydev/…
      • @MikeCheel 为什么你认为resListIAsyncEnumerable?例如,resList.Count() 呼叫中没有等待。
      • @MikeCheel 您链接的文章不是关于问题,而是关于可能的性能提升,这是有争议的,因为很大程度上取决于场景。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-02
      • 1970-01-01
      • 2017-02-08
      • 1970-01-01
      • 2015-08-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多