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
,它采用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); }