답변:
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
결과를 생성하는 작업 (관리자)처럼 들리며, 이러한 작업 이 값을 부작용으로 설정하는 것보다 결과를 반환 할 수 있다면 그러한 코드가 더 자연 스럽 습니다.async
. 그들은 매우 도움이되었습니다!
foreach
와 await
루프 본문입니다.
ForEach
동기식 대리자 유형 만 취하며 비동기 대리자 유형을 취하는 오버로드는 없습니다. 따라서 짧은 대답은 "아무도 비동기식을 작성하지 않았습니다 ForEach
"입니다. 더 긴 대답은 어떤 의미론을 가정해야한다는 것입니다. 예를 들어 항목을 한 번에 하나씩 (예 foreach
:) 또는 동시에 (예 :) 처리해야 Select
합니까? 한 번에 하나씩 비동기 스트림이 더 나은 솔루션이 아닐까요? 동시에 결과가 원래 항목 순서 여야합니까 아니면 완료 순서 여야합니까? 첫 번째 실패에서 실패해야합니까 아니면 모두 완료 될 때까지 기다려야합니까? 기타
SemaphoreSlim
비동기 작업을 제한 하는 데 사용 합니다.
이 작은 확장 메서드는 예외로부터 안전한 비동기 반복을 제공해야합니다.
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);
});
async
하기 전에해야한다i =>
ForEachAsync
동작중인 아마 구성해야하므로, 기본적으로 라이브러리 방법이다 ConfigureAwait(false)
.
간단한 대답은 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;
}
}
MoveNext
열거자를 기다리는 이점이 있습니다 . 이는 열거자가 다음 요소를 즉시 가져올 수없고 사용할 수있을 때까지 기다려야하는 경우에 중요합니다.
이 확장 방법 추가
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);
});
async void
. 이 접근 방식에는 예외 처리 및 비동기 작업이 완료되는시기를 아는 데 문제가 있습니다.
List.ForEach()
LINQ의 일부가 아닙니다.