繁体   English   中英

在 ForEach 中等待异步的正确方法是什么?

[英]What is the proper way to await for async inside of a ForEach?

.NET 核心 2.2,VS 2019

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

这是代码:

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

这是因为ForEach<T>只是执行该async函数,但没有等待它们完成。

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

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


 await Task.WhenAll(tasks);

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

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

补充说明:

ForEach<T>的实现:

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

如您所见,它只是执行该操作,但没有等待它们完成。 实际上,它不能。 Action什么也不返回。 但是等待Task必须返回。 因此,为了使ForEach函数在这种情况下有用,它必须采用返回TaskFunc然后在内部for迭代器。

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

void-returning async 方法有一个特定的目的:使异步事件处理程序成为可能。

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

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

Eric Lippert 有一篇很好的博客文章说明了为什么.ForEach()方法不是那么好,这是一个完美的例子,说明了foreach循环如何优于.ForEach - foreach将允许您实际等待操作。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM