C# ForEach()方法中的异步lambda是如何处理的?

C# ForEach()方法中的异步lambda是如何处理的?,c#,linq,async-await,C#,Linq,Async Await,我们在产品中遇到了一个bug,并将其归结为以下问题。 给定一个列表并使用异步lambda调用ForEach扩展方法,输出的预期顺序是什么: public static async Task Main() { var strings = new List<string> { "B", "C", "D" }; Console.WriteLine("A"); strings.ForEach(async s => { await AsyncMethod(s);

我们在产品中遇到了一个bug,并将其归结为以下问题。 给定一个列表并使用异步lambda调用ForEach扩展方法,输出的预期顺序是什么:

public static async Task Main()
{
    var strings = new List<string> { "B", "C", "D" };
    Console.WriteLine("A");
    strings.ForEach(async s => { await AsyncMethod(s); } );
    Console.WriteLine("E");
}

private static async Task AsyncMethod(string s)
{
    await Task.Run(() => { Console.WriteLine(s); });
}
有人能解释一下区别在哪里吗?这些异步lambda是如何执行的,为什么不等待它们


澄清:问题不是B、C和D的顺序。问题是E在循环完成之前出现

问题是由于没有等待列表中每个字符串上的任务完成

      Console.WriteLine("A");
      strings.ForEach( s => { AsyncMethod(s).Wait(); });

      Console.WriteLine("E");

上述解决方案工作正常。希望有帮助

ForEach
不支持
async
委托,因为它执行
操作
。这会将您的方法减少到不可等待的
异步void
ForEach
从未用于
Func
或任何其他异步变体。在
asynchmethod
上调用
.Wait
将导致单线程同步上下文死锁。不过,您有一些选择:

  • 标准
    ForEach
你误解了这是怎么回事。以下是按顺序采取的步骤:

  • 异步处理“B”
  • 等待(1)
  • 异步处理“C”
  • 等待(3)
  • 异步处理“D”
  • 等待(5)
  • wait
    是每个迭代的一部分。在当前迭代完成之前,下一个迭代不会开始

    由于不异步处理任务,这些顺序任务将按启动顺序完成


    另一方面,这种方法的工作原理不同:

  • 异步处理“B”
  • 异步处理“C”
  • 异步处理“D”
  • ForEach
    启动任务,但不会立即等待它们。由于异步处理的性质,这些并发任务可以在每次运行代码时以不同的顺序完成

    由于没有任何东西等待由
    ForEach
    生成的任务,因此“E”任务立即启动。BCDE都是异步处理的,可以按任意顺序完成


    您可以重新设计您的
    foreach
    示例,以匹配您的
    foreach
    示例:

    foreach (var s in strings) 
    {
        AsyncMethod(s);
    }
    
    现在,处理与ForEach中的处理相同:

  • 异步处理“B”
  • 异步处理“C”
  • 异步处理“D”

  • 但是,如果要确保只有在BCD全部完成时才启动E任务,只需将BCD任务保存在一个集合中,等待它们在一起:

    foreach (var s in strings) 
    {
        myTaskList.Add(AsyncMethod(s));
    }
    
    await Task.WhenAll(myTaskList);
    
  • 异步处理“B”,并将其任务添加到列表中
  • 异步处理“C”,并将其任务添加到列表中
  • 异步处理“D”,并将其任务添加到列表中
  • 在执行任何其他操作之前,请等待列表中的所有任务

  • 那么您可能必须添加一个continuationA continuation on what?我认为这是因为
    ForEach
    方法将
    Action
    作为参数,并且
    Action
    不是异步的。因此,每个任务都已启动但未等待。
    ForEach
    不适用于
    async
    方法,调用
    .Wait()
    可能会导致死锁。您显示的第二个代码段并不像您所说的那样等待所有任务。您的第二个示例不正确,没有等待1、2、3的内容。该方法立即继续通过
    ForEach
    ,因为它返回一个
    async void
    @Servy:我已经在纠正它了,你是对的。我太专注于解释为什么
    foreach
    等待每一次迭代,而我忽略了
    foreach
    中缺少等待的原因:)@JSteward:请看上面的评论。答案很好!类似的可读代码(即
    .ForEach
    vs
    ForEach
    )有时会忽略这些缺陷。我认为这是我的关键点。您可以在ForEach中放置异步委托,但它们只是被调用而不是等待。
    strings.ForEach(async s => { await AsyncMethod(s); });
    
    foreach (var s in strings) 
    {
        AsyncMethod(s);
    }
    
    foreach (var s in strings) 
    {
        myTaskList.Add(AsyncMethod(s));
    }
    
    await Task.WhenAll(myTaskList);