[C#] linq select에서 비동기식 대기

기존 프로그램을 수정해야하며 다음 코드가 포함되어 있습니다.

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

그러나이 먼저 모든 사용, 나에게 매우 이상한 것 같습니다 asyncawait선택에. Stephen Cleary 의이 답변 에 따르면 나는 그것들을 떨어 뜨릴 수 있어야합니다.

그런 다음 두 번째 Select결과를 선택합니다. 이것은 작업이 전혀 비동기식이 아니며 동기식으로 수행되거나 (아무것도 많은 노력을 기울이지 않음) 작업이 비동기식으로 수행되고 완료되면 나머지 쿼리가 실행되는 것을 의미하지 않습니까?

나는에 따라 다음과 같이 위의 코드를 작성해야 스티븐 클리 어리하여 다른 답변 :

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

그리고 이것과 완전히 동일합니까?

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

이 프로젝트에서 작업하는 동안 첫 번째 코드 샘플을 변경하고 싶지만 비동기 코드 변경 (거의 작동)에 너무 열중하지 않습니다. 어쩌면 나는 아무것도 걱정하지 않고 3 개의 코드 샘플이 모두 똑같은 일을합니까?

ProcessEventsAsync는 다음과 같습니다.

async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}



답변

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

그러나 이것은 나에게 매우 이상하게 보입니다. 먼저 async를 사용하고 선택을 기다립니다. Stephen Cleary 의이 답변에 따르면 나는 그것들을 떨어 뜨릴 수 있어야합니다.

에 대한 통화 Select가 유효합니다. 이 두 줄은 기본적으로 동일합니다.

events.Select(async ev => await ProcessEventAsync(ev))
events.Select(ev => ProcessEventAsync(ev))

(에서 동기식 예외가 발생하는 방법에 대해서는 약간의 차이가 ProcessEventAsync있지만이 코드의 맥락에서는 전혀 중요하지 않습니다.)

그런 다음 결과를 선택하는 두 번째 선택. 이것은 작업이 전혀 비동기식이 아니며 동기식으로 수행되거나 (아무것도 많은 노력을 기울이지 않음) 작업이 비동기식으로 수행되고 완료되면 나머지 쿼리가 실행되는 것을 의미하지 않습니까?

이는 쿼리가 차단되고 있음을 의미합니다. 따라서 실제로 비동기 적이 지 않습니다.

세분화 :

var inputs = events.Select(async ev => await ProcessEventAsync(ev))

먼저 각 이벤트에 대해 비동기 작업을 시작합니다. 그런 다음이 줄 :

                   .Select(t => t.Result)

해당 작업이 한 번에 하나씩 완료 될 때까지 기다립니다 (먼저 첫 번째 이벤트의 작업을 기다린 후 다음, 다음에 다음 등).

이 부분은에서 예외를 차단하고 래핑하기 때문에 내가 신경 쓰지 않는 부분입니다 AggregateException.

그리고 이것과 완전히 동일합니까?

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

예,이 두 예는 동일합니다. 둘 다 모든 비동기 작업을 시작한 events.Select(...)다음 ( ) 모든 작업이 임의의 순서로 완료 될 때까지 비동기 적으로 기다린 다음 ( await Task.WhenAll(...)) 나머지 작업을 진행합니다 ( Where...).

이 두 예제는 원래 코드와 다릅니다. 원본 코드가 차단되어에서 예외를 래핑 AggregateException합니다.


답변

기존 코드는 작동하지만 스레드를 차단하고 있습니다.

.Select(async ev => await ProcessEventAsync(ev))

모든 이벤트에 대해 새 작업을 생성하지만

.Select(t => t.Result)

스레드가 새 작업이 끝날 때까지 기다리는 것을 차단합니다.

반면에 코드는 동일한 결과를 생성하지만 비동기 적으로 유지합니다.

첫 번째 코드에 대한 하나의 주석. 이 라인

var tasks = await Task.WhenAll(events...

단일 작업을 생성하므로 변수의 이름을 단수로 지정해야합니다.

마지막으로 마지막 코드는 동일하지만 간결합니다.

참조 : Task.Wait / Task.WhenAll


답변

Linq에서 사용할 수있는 현재 방법을 사용하면 꽤 추한 것처럼 보입니다.

var tasks = items.Select(
    async item => new
    {
        Item = item,
        IsValid = await IsValid(item)
    });
var tuples = await Task.WhenAll(tasks);
var validItems = tuples
    .Where(p => p.IsValid)
    .Select(p => p.Item)
    .ToList();

다행스럽게도 다음 .NET 버전은 작업 모음과 컬렉션 작업을 처리 할 수있는보다 세련된 도구를 제공 할 것입니다.


답변

나는이 코드를 사용했다 :

public static async Task<IEnumerable<TResult>> SelectAsync<TSource,TResult>(this IEnumerable<TSource> source, Func<TSource, Task<TResult>> method)
{
      return await Task.WhenAll(source.Select(async s => await method(s)));
}

이처럼 :

var result = await sourceEnumerable.SelectAsync(async s=>await someFunction(s,other params));


답변

나는 이것을 확장 방법으로 선호한다 :

public static async Task<IEnumerable<T>> WhenAll<T>(this IEnumerable<Task<T>> tasks)
{
    return await Task.WhenAll(tasks);
}

메소드 체인과 함께 사용할 수 있습니다.

var inputs = await events
  .Select(async ev => await ProcessEventAsync(ev))
  .WhenAll()


답변