我有以下正确使用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);
});
}
我担心这种方法没有异步签名,只有正文。 这是我上面第一段代码的正确等价吗?
答案 0 :(得分:18)
没有。事实并非如此。此Product
不支持ForEach
,并且要求您的lambda为async-await
,仅才能用于事件处理程序。使用它将同时运行所有async void
操作,并且不会等待它们完成。
您可以使用常规async
,但如果您需要扩展方法,则需要特殊的foreach
版本。
您可以自己创建一个迭代项目,执行async
操作并async
它:
await
用法:
public async Task ForEachAsync<T>(this IEnumerable<T> enumerable, Func<T, Task> 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
正如评论中所指出的,您可能不希望在foreach中使用public Task ForEachAsync<T>(this IEnumerable<T> enumerable, Func<T, Task> action)
{
return Task.WhenAll(enumerable.Select(item => action(item)));
}
。准备更改然后立即保存所有更改可能会更有效。
答案 1 :(得分:0)
foreach
的初始示例在每次循环迭代后有效等待。
最后一个示例调用带有List<T>.ForEach()
含义的Action<T>
,即异步lambda将编译为void
委托,而不是标准Task
。
实际上,ForEach()
方法将调用&#34;迭代&#34;一个接一个,没有等待每个人完成。这也将传播到您的方法,这意味着AddReferenceData()
可能在工作完成之前完成。
所以不,它不是等效的,行为完全不同。实际上,假设它是一个EF上下文,它会爆炸,因为它可能不会同时跨多个线程使用。
另请阅读Deepu提到的http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx,了解为什么坚持foreach
可能会更好。
答案 2 :(得分:0)
要在方法中编写await,您的方法需要标记为async。 当您编写ForEach方法时,您正在编写等于您的labda表达式的内容,这与您从方法中调用的方法完全不同。您需要将此lamdba表达式移动到方法并将该方法标记为异步,并且@ i3arnon表示您需要异步标记ForEach方法,该方法尚未由.Net Framework提供。所以你需要自己写。
答案 3 :(得分:0)
感谢大家的反馈。在循环外部使用&#34; save&#34; 部分,我相信以下两种方法现在是等效的,一种使用foreach()
,另一种使用.ForEach()
。但是,正如Deepu所提到的,我将阅读Eric关于为什么foreach
可能更好的帖子。
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);
}
答案 4 :(得分:0)
为什么不使用AddRange()方法?
context.SinkTypeCollection.AddRange(RequiredSinkTypeList.Select( sinkName => new SinkType() { Name = sinkName } );
await context.SaveChangesAsync().ConfigureAwait(false);