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


87

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

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

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


DoSomethingAsync(foo)각 foo에 대해 순차적 으로 처리해야합니까 , 아니면 Parallel.ForEach <Foo> 의 후보 입니까?
mdisibio

1
@mdisibio- Parallel.ForEach차단 중입니다. 이 패턴 은 Pluralsight에 대한 Jon Skeet의 비동기 C # 비디오에서 가져온 것 입니다. 차단없이 병렬로 실행됩니다.
Matt Johnson-Pint

@mdisibio-아니. 그들은 병렬로 실행됩니다. 그것을 시도하십시오 . (I 필요하지 않은 것처럼 또한, 보이는 .ToList()난 그냥 사용에 갈거야 경우 WhenAll.)
매트 존슨 - 파인트

요점을 알았어. DoSomethingAsync작성 방법에 따라 목록이 병렬로 실행되거나 실행되지 않을 수 있습니다. 나는 테스트 메서드와 그렇지 않은 버전을 작성할 수 있었지만 두 경우 모두 작업을 생성하는 대리자가 아닌 메서드 자체에 의해 동작이 결정됩니다. 혼동해서 죄송합니다. 그러나 DoSomethingAsycreturn Task<Foo>이면 await델리게이트의는 절대적으로 필요하지 않습니다. 그것이 제가 만들려고했던 요점이라고 생각합니다.
mdisibio

답변:


136

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

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

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

var results = await Task.WhenAll(tasks);

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

return await Task.WhenAll(tasks);

11
이것도 작동 할 수 있음을 지적하고 싶었습니다var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
mdisibio

1
또는 심지어var tasks = foos.Select(DoSomethingAsync).ToList();
Todd Menier 2014-08-20

3
Linq가 비동기 코드에서 완벽하게 작동하지 않는 이유는 무엇입니까?
Ehsan Sajjad

2
@EhsanSajjad : LINQ to Objects는 메모리 내 개체에서 동시에 작동하기 때문입니다. 일부 제한 가지 작업처럼 Select. 그러나 대부분은 Where.
Stephen Cleary

4
@EhsanSajjad : 작업이 I / O 기반이면 async스레드를 줄이는 데 사용할 수 있습니다 . CPU 바운드이고 이미 백그라운드 스레드에 async있으면 이점을 제공하지 않습니다.
Stephen Cleary

9

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

10
개인적으로, 나는 당신의 확장 메서드의 이름을 것이다ToArrayAsync
Torvin 사용자

4

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가 느리게 적중됩니다.


1

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


1
그것도 작동하지 않습니다. Task.WaitAll기다릴 수없는 차단이며 Task<T>.
Matt Johnson-Pint

@MattJohnson WhenAll?
LB

네. 그게 다야! 나는 바보 같다. 감사!
Matt Johnson-Pint

0

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.