[c#] LINQ를 사용하여 비동기 적으로 작업 목록을 기다리는 방법은 무엇입니까?

다음과 같이 만든 작업 목록이 있습니다.

public async Task<IList<Foo>> GetFoosAndDoSomethingAsync()
{
    var foos = await GetFoosAsync();

    var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();

    ...
}

를 사용 .ToList()하면 작업이 모두 시작됩니다. 이제 완료를 기다리고 결과를 반환하고 싶습니다.

이것은 위의 ...블록 에서 작동합니다 .

var list = new List<Foo>();
foreach (var task in tasks)
    list.Add(await task);
return list;

내가 원하는 것을하지만 이것은 다소 서투른 것 같습니다. 나는 차라리 다음과 같이 더 간단한 것을 작성하고 싶습니다.

return tasks.Select(async task => await task).ToList();

…하지만 이것은 컴파일되지 않습니다. 내가 무엇을 놓치고 있습니까? 아니면 이런 식으로 표현하는 것이 불가능합니까?



답변

LINQ는 async코드에서 완벽하게 작동하지 않지만 다음과 같이 할 수 있습니다.

var tasks = foos.Select(DoSomethingAsync).ToList();
await Task.WhenAll(tasks);

작업이 모두 동일한 유형의 값을 반환하는 경우 다음과 같이 할 수도 있습니다.

var results = await Task.WhenAll(tasks);

꽤 좋습니다. WhenAll배열을 반환하므로 메서드가 결과를 직접 반환 할 수 있다고 생각합니다.

return await Task.WhenAll(tasks);


답변

Stephen의 답변을 확장하기 위해 LINQ 의 유창한 스타일 을 유지하기 위해 다음 확장 메서드를 만들었습니다 . 그런 다음 할 수 있습니다.

await someTasks.WhenAll()

namespace System.Linq
{
    public static class IEnumerableExtensions
    {
        public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source)
        {
            return Task.WhenAll(source);
        }
    }
}


답변

Task.WhenAll의 한 가지 문제는 병렬 처리를 생성한다는 것입니다. 대부분의 경우 더 좋을 수도 있지만 때로는 피하고 싶을 때도 있습니다. 예를 들어, DB에서 일괄 데이터를 읽고 일부 원격 웹 서비스로 데이터를 전송합니다. 모든 배치를 메모리에로드하고 싶지는 않지만 이전 배치가 처리되면 DB에 도달합니다. 따라서 비동기 성을 깨야합니다. 다음은 그 예입니다.

var events = Enumerable.Range(0, totalCount/ batchSize)
   .Select(x => x*batchSize)
   .Select(x => dbRepository.GetEventsBatch(x, batchSize).GetAwaiter().GetResult())
   .SelectMany(x => x);
foreach (var carEvent in events)
{
}

참고 .GetAwaiter (). GetResult ()는 동기식으로 변환합니다. 이벤트의 batchSize가 처리 된 후에 만 ​​DB가 느리게 적중됩니다.


답변

Task.WaitAll또는 Task.WhenAll적절한 것을 사용하십시오 .


답변

Task.WhenAll이 여기서 트릭을해야합니다.


답변