C#async等待使用LINQ ForEach()

Sam*_*evx 15 .net c# linq async-await

我有以下代码正确使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果不是使用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);
        });
}
Run Code Online (Sandbox Code Playgroud)

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

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);
        });
}
Run Code Online (Sandbox Code Playgroud)

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

i3a*_*non 19

不,不是.这ForEach不支持async-await并且要求您的lambda 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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);
    });
}
Run Code Online (Sandbox Code Playgroud)

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

public Task ForEachAsync<T>(this IEnumerable<T> enumerable, Func<T, Task> action)
{
    return Task.WhenAll(enumerable.Select(item => action(item)));
}
Run Code Online (Sandbox Code Playgroud)

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


Jac*_*goń 1

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

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

所以不,它不是等价的,并且行为完全不同。事实上,假设它是一个 EF 上下文,它将会崩溃,因为它可能无法在多个线程中同时使用。

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