如何在ForEach中使用Async?


123

使用ForEach时是否可以使用Async?下面是我正在尝试的代码:

using (DataContext db = new DataLayer.DataContext())
{
    db.Groups.ToList().ForEach(i => async {
        await GetAdminsFromGroup(i.Gid);
    });
}

我收到错误消息:

名称“异步”在当前上下文中不存在

包含using语句的方法设置为async。

Answers:


180

List<T>.ForEach不能特别好地使用async(出于相同的原因,LINQ到对象也没有)。

在这种情况下,我建议将每个元素投影到一个异步操作中,然后您可以(异步)等待它们全部完成。

using (DataContext db = new DataLayer.DataContext())
{
    var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
    var results = await Task.WhenAll(tasks);
}

async授予代表相比,此方法的好处ForEach是:

  1. 错误处理更合适。async void无法捕获来自的异常catch;这种方法将在线传播异常await Task.WhenAll,从而允许自然的异常处理。
  2. 您知道此方法结束时任务已完成,因为它执行await Task.WhenAll。如果使用async void,则无法轻松判断操作何时完成。
  3. 这种方法具有检索结果的自然语法。GetAdminsFromGroupAsync听起来像是一个产生结果的操作(管理员),并且如果这样的操作可以返回其结果而不是将值设置为副作用,则这种代码更为自然。

5
并不是说它会改变任何东西,但List.ForEach()它不是LINQ的一部分。
2013年

很好的建议@StephenCleary,并感谢您提供的所有答案async。他们非常有帮助!
Justin Helgerson 2014年

4
@StewartAnderson:这些任务将同时执行。串行执行没有扩展。只是做了foreachawait你的循环体。
史蒂芬·克雷里

1
@mare:ForEach仅采用同步委托类型,并且没有过载采用异步委托类型。因此,简短的答案是“没有人写异步ForEach”。更长的答案是,您必须假设一些语义。例如,是否应一次(如foreach)或同时(如Select)处理这些项目?如果一次使用一个,异步流不是更好的解决方案吗?如果同时出现,结果应该按原始项目顺序还是按完成顺序排列?它应该在第一次失败时失败还是要等到全部完成之后才能失败?等等
Stephen Cleary

2
@RogerWolf:是的;用于SemaphoreSlim限制异步任务。
Stephen Cleary

61

这个扩展方法应该可以为您提供异常安全的异步迭代:

public static async Task ForEachAsync<T>(this List<T> list, Func<T, Task> func)
{
    foreach (var value in list)
    {
        await func(value);
    }
}

由于我们将Lambda的返回类型从void更改为Task,因此异常会正确传播。这样您就可以在实践中编写如下内容:

await db.Groups.ToList().ForEachAsync(async i => {
    await GetAdminsFromGroup(i.Gid);
});

我相信async应该早于i =>
Todd

除了等待ForEachAsyn(),还可以调用Wait()。
乔纳斯(Jonas)

Lambda不需要在这里等待。
hazzik '17

我将在其中添加对CancellationToken的支持,如此处的托德的答案stackoverflow.com/questions/29787098/…–
Zorkind

ForEachAsync本质上是一个库的方法,所以等待应该与可能配置ConfigureAwait(false)
Theodor Zoulias

9

简单的答案是使用foreach关键字而不是的ForEach()方法List()

using (DataContext db = new DataLayer.DataContext())
{
    foreach(var i in db.Groups)
    {
        await GetAdminsFromGroup(i.Gid);
    }
}

你真是个天才
Vick_onrails

8

这是上述异步foreach变体的实际工作版本,具有顺序处理功能:

public static async Task ForEachAsync<T>(this List<T> enumerable, Action<T> action)
{
    foreach (var item in enumerable)
        await Task.Run(() => { action(item); }).ConfigureAwait(false);
}

这是实现:

public async void SequentialAsync()
{
    var list = new List<Action>();

    Action action1 = () => {
        //do stuff 1
    };

    Action action2 = () => {
        //do stuff 2
    };

    list.Add(action1);
    list.Add(action2);

    await list.ForEachAsync();
}

主要区别是什么?.ConfigureAwait(false);它在异步顺序处理每个任务时保留主线程的上下文。


6

从开始C# 8.0,您可以异步创建和使用流。

    private async void button1_Click(object sender, EventArgs e)
    {
        IAsyncEnumerable<int> enumerable = GenerateSequence();

        await foreach (var i in enumerable)
        {
            Debug.WriteLine(i);
        }
    }

    public static async IAsyncEnumerable<int> GenerateSequence()
    {
        for (int i = 0; i < 20; i++)
        {
            await Task.Delay(100);
            yield return i;
        }
    }

更多


1
这样做的好处是,除了等待每个元素之外,您现在还正在等待MoveNext枚举数。这在枚举器无法立即获取下一个元素并且必须等待一个元素可用之前非常重要。
Theodor Zoulias

3

添加此扩展方法

public static class ForEachAsyncExtension
{
    public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
    {
        return Task.WhenAll(from partition in Partitioner.Create(source).GetPartitions(dop) 
            select Task.Run(async delegate
            {
                using (partition)
                    while (partition.MoveNext())
                        await body(partition.Current).ConfigureAwait(false);
            }));
    }
}

然后像这样使用:

Task.Run(async () =>
{
    var s3 = new AmazonS3Client(Config.Instance.Aws.Credentials, Config.Instance.Aws.RegionEndpoint);
    var buckets = await s3.ListBucketsAsync();

    foreach (var s3Bucket in buckets.Buckets)
    {
        if (s3Bucket.BucketName.StartsWith("mybucket-"))
        {
            log.Information("Bucket => {BucketName}", s3Bucket.BucketName);

            ListObjectsResponse objects;
            try
            {
                objects = await s3.ListObjectsAsync(s3Bucket.BucketName);
            }
            catch
            {
                log.Error("Error getting objects. Bucket => {BucketName}", s3Bucket.BucketName);
                continue;
            }

            // ForEachAsync (4 is how many tasks you want to run in parallel)
            await objects.S3Objects.ForEachAsync(4, async s3Object =>
            {
                try
                {
                    log.Information("Bucket => {BucketName} => {Key}", s3Bucket.BucketName, s3Object.Key);
                    await s3.DeleteObjectAsync(s3Bucket.BucketName, s3Object.Key);
                }
                catch
                {
                    log.Error("Error deleting bucket {BucketName} object {Key}", s3Bucket.BucketName, s3Object.Key);
                }
            });

            try
            {
                await s3.DeleteBucketAsync(s3Bucket.BucketName);
            }
            catch
            {
                log.Error("Error deleting bucket {BucketName}", s3Bucket.BucketName);
            }
        }
    }
}).Wait();

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.