Как я могу использовать Async с ForEach?

123

Можно ли использовать Async при использовании ForEach? Ниже приведен код, который я пытаюсь выполнить:

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

Я получаю сообщение об ошибке:

Имя Async не существует в текущем контексте.

Метод, в который заключен оператор using, имеет значение async.

Джеймс Джеффри
источник

Ответы:

180

List<T>.ForEachне особенно хорошо работает с async(по тем же причинам и LINQ-to-objects).

В этом случае я рекомендую спроецировать каждый элемент в асинхронную операцию, а затем вы можете (асинхронно) дождаться их завершения.

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.
svick 07
Отличное предложение @StephenCleary и спасибо за все ответы, которые вы дали async. Они мне очень помогли!
Джастин Хелгерсон
4
@StewartAnderson: задачи будут выполняться одновременно. Нет расширения для серийного исполнения; вобще foreachс awaitвашим телом цикла.
Стивен Клири
1
@mare: ForEachпринимает только синхронный тип делегата, и нет перегрузки, принимающей асинхронный тип делегата. Итак, краткий ответ: «никто не писал асинхронный ForEach». Более длинный ответ заключается в том, что вам придется предположить некоторую семантику; например, должны ли элементы обрабатываться по одному (например foreach) или одновременно (например Select)? Разве асинхронные потоки по одному - не лучшее решение? Если одновременно, должны ли результаты быть в исходном порядке элементов или в порядке завершения? Должен ли он выйти из строя при первом сбое или дождаться завершения всех? И т.д.
Стивен Клири
2
@RogerWolf: Да; используйте SemaphoreSlimдля ограничения асинхронных задач.
Стивен Клири,
61

Этот небольшой метод расширения должен дать вам безопасную в отношении исключений асинхронную итерацию:

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

Поскольку мы меняем тип возвращаемого значения лямбды с voidна Task, исключения будут распространяться правильно. Это позволит вам на практике написать что-то вроде этого:

await db.Groups.ToList().ForEachAsync(async i => {
    await GetAdminsFromGroup(i.Gid);
});
JD Courtoy
источник
Я считаю, что asyncдолжно быть раньшеi =>
Тодд
Вместо ожидания ForEachAsyn () можно также вызвать Wait ().
Jonas
Здесь не нужно ждать лямбды.
hazzik
Я бы добавил поддержку 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

Вот актуальная рабочая версия вышеупомянутых вариантов async 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);который сохраняет контекст основного потока при асинхронной последовательной обработке каждой задачи.

mrogunlana
источник
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();
superlogical
источник
2

Проблема заключалась в том, что asyncключевое слово должно стоять перед лямбда, а не перед телом:

db.Groups.ToList().ForEach(async (i) => {
    await GetAdminsFromGroup(i.Gid);
});
Джеймс Джеффри
источник
35
-1 за ненужное и тонкое использование async void. У этого подхода есть проблемы с обработкой исключений и знанием того, когда завершаются асинхронные операции.
Стивен Клири
Да, я обнаружил, что это не обрабатывает исключения должным образом.
Herman Schoenfeld