[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
과 같습니다.
- 오류 처리가 더 적절합니다. 에서 예외를
async void
포착 할 수 없습니다catch
. 이 접근 방식은await Task.WhenAll
라인 에서 예외를 전파 하여 자연스러운 예외 처리를 허용합니다. - 이 메서드는
await Task.WhenAll
. 를 사용async void
하면 작업이 언제 완료되었는지 쉽게 알 수 없습니다. - 이 접근 방식은 결과를 검색하기위한 자연스러운 구문을 가지고 있습니다.
GetAdminsFromGroupAsync
결과를 생성하는 작업 (관리자)처럼 들리며, 이러한 작업 이 값을 부작용으로 설정하는 것보다 결과를 반환 할 수 있다면 그러한 코드가 더 자연 스럽 습니다.
답변
이 작은 확장 메서드는 예외로부터 안전한 비동기 반복을 제공해야합니다.
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);
});
답변
간단한 대답은 foreach
의 ForEach()
방법 대신 키워드 를 사용 하는 것입니다 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);
});