我有以下正确使用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);
}
}
如果我想使用LINQ foreach()而不是使用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);
});
}
我担心这个方法没有异步签名,只有主体有。这是我上面的第一个代码块的正确等效吗?
No。它不是。此ForEach
不支持async-await
,并要求您的lambda为async void
,这应该仅用于事件处理程序。使用它将同时运行所有async
操作,而不会等待它们完成。
你可以使用常规的foreach
,但如果你想要一个扩展方法,你需要一个特殊的async
版本,它迭代项,执行async
操作,await
执行它。
但是,您可以创建一个:
从。net 6.0开始,你可以使用Parallel.ForEachAsync
:public async Task ForEachAsync<T>(this IEnumerable<T> enumerable, Func<T, Task> action)
{
await Parallel.ForEachAsync(
enumerable,
async (item, _) => await action(item));
}
或者,避免使用扩展方法,直接调用它:
await Parallel.ForEachAsync(
RequiredSinkTypeList,
async (sinkName, _) =>
{
var sinkType = new SinkType() { Name = sinkName };
context.SinkTypeCollection.Add(sinkType);
await context.SaveChangesAsync().ConfigureAwait(false);
});
在旧的平台上,你需要使用foreach
:
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
所有操作一起,但这只有在您的操作可以并发运行时才有可能,这并非总是如此(例如实体框架):
public Task ForEachAsync<T>(this IEnumerable<T> enumerable, Func<T, Task> action)
{
return Task.WhenAll(enumerable.Select(item => action(item)));
}
正如在评论中所指出的,您可能不希望在foreach中开始使用SaveChangesAsync
。准备您的更改,然后一次保存它们可能会更有效率。
。Net 6现在有
Parallel.ForEachAsync
使用await Parallel.ForEachAsync(userHandlers, parallelOptions, async (uri, token) =>
{
var user = await client.GetFromJsonAsync<GitHubUser>(uri, token);
Console.WriteLine($"Name: {user.Name}nBio: {user.Bio}");
});
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreachasynchttps://www.hanselman.com/blog/parallelforeachasync-in-net-6
foreach
的初始示例有效地在每次循环迭代之后等待。最后一个示例调用List<T>.ForEach()
,它具有Action<T>
的含义,即您的async lambda将编译成void
委托,而不是标准的Task
。
有效地,ForEach()
方法将一个接一个地调用"迭代",而不必等待每个迭代完成。这也将传播到您的方法,这意味着AddReferenceData()
可能会在工作完成之前完成。
所以不,它不是等价的,并且行为完全不同。事实上,假设它是一个EF上下文,它可能会爆炸,因为它可能不会并发地跨多个线程使用。
也可以阅读Deepu提到的http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx,了解为什么坚持foreach
可能更好。
要在方法中写入await,需要将方法标记为async。当你写ForEach方法时,你是在你的labda表达式中写await,这是你从你的方法中调用的不同方法。你需要将这个lambda表达式移动到方法中,并将该方法标记为async,也正如@i3arnon所说,你需要async标记ForEach方法,这是。net框架尚未提供的。所以你需要自己写
感谢大家的反馈。将"save"部分放在循环之外,我认为以下两个方法现在是等效的,一个使用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);
}
为什么不使用AddRange()方法?
context.SinkTypeCollection.AddRange(RequiredSinkTypeList.Select( sinkName => new SinkType() { Name = sinkName } );
await context.SaveChangesAsync().ConfigureAwait(false);