C#异步等待使用LINQ ForEach()

我有以下代码正确使用async / await范例。

internal static async Task AddReferencseData(ConfigurationDbContext context) { foreach (var sinkName in RequiredSinkTypeList) { var sinkType = new SinkType() { Name = sinkName }; context.SinkTypeCollection.Add(sinkType); await context.SaveChangesAsync().ConfigureAwait(false); } } 

如果不是使用foreach(),我想使用LINQ ForEach(),那么写这个的等效方法是什么? 例如,这个给出了编译错误。

 internal static async Task AddReferenceData(ConfigurationDbContext context) { RequiredSinkTypeList.ForEach( sinkName => { var sinkType = new SinkType() { Name = sinkName }; context.SinkTypeCollection.Add(sinkType); await context.SaveChangesAsync().ConfigureAwait(false); }); } 

我没有编译错误的唯一代码就是这个。

 internal static void AddReferenceData(ConfigurationDbContext context) { RequiredSinkTypeList.ForEach( async sinkName => { var sinkType = new SinkType() { Name = sinkName }; context.SinkTypeCollection.Add(sinkType); await context.SaveChangesAsync().ConfigureAwait(false); }); } 

我担心这种方法没有异步签名,只有正文。 这是我上面第一段代码的正确等价吗?

不,不是。 这个ForEach不支持async-await并且要求你的lambda是async void ,它只能用于事件处理程序。 使用它将同时运行所有async操作,不会等待它们完成。

您可以像使用常规foreach一样使用,但如果需要扩展方法,则需要特殊的async版本。

您可以自己创建一个迭代项目,执行async操作并await它:

 public async Task ForEachAsync(this IEnumerable enumerable, Func action) { foreach (var item in enumerable) { await action(item); } } 

用法:

 internal static async Task AddReferencseData(ConfigurationDbContext context) { await RequiredSinkTypeList.ForEachAsync(async sinkName => { var sinkType = new SinkType() { Name = sinkName }; context.SinkTypeCollection.Add(sinkType); await context.SaveChangesAsync().ConfigureAwait(false); }); } 

ForEachAsync一个不同(通常更有效)的实现是启动所有async操作,然后才将它们全部await在一起,但只有当您的操作可以并发运行时才有可能(但并非总是如此)(例如,entity framework):

 public Task ForEachAsync(this IEnumerable enumerable, Func action) { return Task.WhenAll(enumerable.Select(item => action(item))); } 

正如评论中所指出的,您可能不希望在foreach中使用SaveChangesAsync 。 准备更改然后立即保存所有更改可能会更有效。

要在方法中编写await,您的方法需要标记为async。 当你编写ForEach方法时,你在labda表达式中编写等待,这与你从方法中调用的方法完全不同。 您需要将此lamdba表达式移动到方法并将该方法标记为async,并将@ i3arnon表示为您需要异步标记ForEach方法,该方法尚未由.Net Framework提供。 所以你需要自己写。

foreach的初始示例在每次循环迭代后有效地等待。 最后一个示例调用List.ForEach() ,它采用Action含义,即异步lambda将编译为void委托,而不是标准Task

实际上, ForEach()方法将逐个调用“迭代”而无需等待每个迭代完成。 这也将传播到您的方法,这意味着AddReferenceData()可能在工作完成之前完成。

所以不,它不是一个等价物,行为完全不同 。 事实上,假设它是一个EF上下文,它会爆炸,因为它可能不会同时跨多个线程使用。

另请阅读Deepu提到的http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx ,了解为什么坚持foreach可能会更好。

为什么不使用AddRange()方法?

 context.SinkTypeCollection.AddRange(RequiredSinkTypeList.Select( sinkName => new SinkType() { Name = sinkName } ); await context.SaveChangesAsync().ConfigureAwait(false); 

谢谢大家的反馈。 在循环外部使用“save”部分,我相信以下两种方法现在是等效的,一种使用foreach() ,另一种使用.ForEach() 。 然而,正如Deepu所提到的,我将阅读Eric关于为什么foreach可能会更好的post。

 public static async Task AddReferencseData(ConfigurationDbContext context) { RequiredSinkTypeList.ForEach( sinkName => { var sinkType = new SinkType() { Name = sinkName }; context.SinkTypeCollection.Add(sinkType); }); await context.SaveChangesAsync().ConfigureAwait(false); } public static async Task AddReferenceData(ConfigurationDbContext context) { foreach (var sinkName in RequiredSinkTypeList) { var sinkType = new SinkType() { Name = sinkName }; context.SinkTypeCollection.Add(sinkType); } await context.SaveChangesAsync().ConfigureAwait(false); }