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

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

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

오류가 발생합니다.

현재 컨텍스트에 ‘Async’라는 이름이 없습니다.

using 문이 포함 된 메서드는 async로 설정됩니다.



답변

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결과를 생성하는 작업 (관리자)처럼 들리며, 이러한 작업 이 값을 부작용으로 설정하는 것보다 결과를 반환 할 수 있다면 그러한 코드가 더 자연 스럽 습니다.

답변

이 작은 확장 메서드는 예외로부터 안전한 비동기 반복을 제공해야합니다.

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


답변

간단한 대답은 foreachForEach()방법 대신 키워드 를 사용 하는 것입니다 List().

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


답변

다음은 순차 처리를 사용하는 위의 비동기 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);각 작업의 비동기 순차 처리 동안 메인 스레드의 컨텍스트를 유지합니다.


답변

로 시작 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;
        }
    }


답변

이 확장 방법 추가

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


답변

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

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