在ForEach内部等待/异步的正确等待是什么?

时间:2019-12-29 09:34:18

标签: c# asynchronous async-await

.NET Core 2.2,VS 2019

问题是UpdateDatabaseWithFile从未执行过?我在这里做错了什么?我试图用等待包装Directory,但是我不能,因为它返回void。正确的方法是什么?

代码如下:

Directory
   .GetFiles(@"C:\Temp")
   .ToList()
   .ForEach(async file =>
   {
        await this._dataService.UpdateDatabaseWithFile();
   });

3 个答案:

答案 0 :(得分:5)

这是因为ForEach<T>仅执行了async个函数,而没有等待它们完成。

您可以使用Select投影必须执行的任务,然后使用Task.WhenAll()执行该功能并等待其完成:

 var tasks = Directory
        .GetFiles(@"C:\Temp")
        .Select(async file =>
        {
            await this._dataService.UpdateDatabaseWithFile();
        });


 await Task.WhenAll(tasks);

或者,如果要顺序执行函数,则可以使用简单的Task.WhenAll()代替foreach

 foreach (var file in Directory.GetFiles(@"C:\Temp"))
 {
      await this._dataService.UpdateDatabaseWithFile();
 }

其他说明:

HereForEach<T>的实现:

 for(int i = 0 ; i < array.Length; i++) {
       action(array[i]);
  }

如您所见,它只是执行该操作,而没有等待其完成。实际上,它不能。 Action不返回任何内容。但是必须等待Task返回。因此,为了使ForEach函数在这种情况下有用,必须使用Func并返回Task,然后在for迭代器中等待。.

答案 1 :(得分:1)

方法List.ForEach不理解异步委托,换句话说,它没有接受Func<Task<T>>参数的重载。在这些情况下,发生的情况是将提供的异步lambda视为async voidAsync voids are evil,因为无法等待它们,所以它们的执行无法与程序的其他部分协调。它们类似于“一劳永逸”的任务,但有一个额外的缺点,即在发生异常情况时终止进程。

  

返回无效的异步方法有一个特定目的:使异步事件处理程序成为可能。

您应注意尝试使用异步委托作为参数调用的方法的签名。诸如Task.Run之类的某些方法对异步委托具有特定的重载,并能很好地处理它们。诸如LINQ Select之类的其他方法在设计时并未考虑异步等待,但至少它们接受类型为Func而不是Action的参数,因此将返回生成的任务,并且可以收集并正确等待。带有List.ForEach参数的Action之类的方法永远不能与异步委托一起使用。

答案 2 :(得分:-2)

这里最可能的问题是.ForEach()无法让您真正等待操作完成。它将执行它们并继续前进。

埃里克·利珀特(Eric Lippert)提出了a good blog post来说明为什么.ForEach()方法并不那么出色,这是foreach循环优于.ForEach的一个完美示例- foreach将允许您实际等待操作。

只需使用循环,问题就会消失:

foreach (var file = Directory.GetFiles(@"C:\Temp"))
{
    await this._dataService.UpdateDatabaseWithFile();
}

应该注意,您似乎实际上并没有在任何地方使用file变量,这似乎是一个问题。希望只是一个错字?