ForEach에서 Async를 어떻게 사용할 수 있습니까?


123

ForEach를 사용할 때 Async를 사용할 수 있습니까? 아래는 내가 시도하는 코드입니다.

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 2013 년

@StephenCleary에 대한 훌륭한 제안과 귀하가 제공 한 모든 답변에 감사드립니다 async. 그들은 매우 도움이되었습니다!
Justin Helgerson 2014

4
@StewartAnderson : 작업이 동시에 실행됩니다. 직렬 실행에 대한 확장은 없습니다. 단지을 foreachawait루프 본문입니다.
Stephen Cleary

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);
    }
}

우리가에서 람다의 반환 유형을 변경하고 이후 voidTask예외가 올바르게 전파됩니다. 이렇게하면 실제로 다음과 같이 작성할 수 있습니다.

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

내가 생각 async하기 전에해야한다i =>
토드

ForEachAsyn ()을 기다리는 대신 Wait ()를 호출 할 수도 있습니다.
Jonas

여기에서 Lambda를 기다릴 필요가 없습니다.
hazzik

나는 여기에 Todd의 답변에서와 같이 CancellationToken에 대한 지원을 추가 할 것입니다. stackoverflow.com/questions/29787098/…
Zorkind

ForEachAsync동작중인 아마 구성해야하므로, 기본적으로 라이브러리 방법이다 ConfigureAwait(false).
Theodor Zoulias

9

간단한 대답은 foreachForEach()방법 대신 키워드 를 사용 하는 것입니다 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();

2

문제는 async키워드가 본문이 아닌 람다 앞에 나타나야한다는 것입니다.

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

35
-1 : async void. 이 접근 방식에는 예외 처리 및 비동기 작업이 완료되는시기를 아는 데 문제가 있습니다.
Stephen Cleary 2013 년

예, 예외를 제대로 처리하지 못하는 것으로 나타났습니다.
Herman Schoenfeld 2013 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.