linq select에서 비동기식 대기


180

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

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) {...}

ProceesEventAsync의 반환 유형은 무엇입니까?
tede24

@ tede24은의 Task<InputResult>InputResult사용자 정의 클래스 인.
Alexander Derck

내 의견으로는 귀하의 버전을 훨씬 쉽게 읽을 수 있습니다. 그러나 Select이전의 작업 결과를 잊어 버렸습니다 Where.
Max

그리고 InputResult에는 Result 속성이 있습니까?
tede24

@ tede24 결과는 내 클래스가 아닌 작업의 속성입니다. 그리고 @Max는 await를 내가 액세스하지 않고 결과를 얻을 수 있는지 확인해야 Result작업의 속성을
알렉산더 Derck을

답변:


185
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합니다.


정리 해줘서 건배! 따라서 예외로 싸인 대신 AggregateException두 번째 코드에서 여러 개의 별도 예외가 발생합니까?
Alexander Derck

1
@AlexanderDerck : 아니요, 이전 코드와 새 코드 모두에서 첫 번째 예외 만 발생합니다. 그러나 Result그것으로 싸여있을 것입니다 AggregateException.
Stephen Cleary

이 코드를 사용하여 ASP.NET MVC 컨트롤러에서 교착 상태가 발생합니다. Task.Run (…)을 사용하여 해결했습니다. 나는 그것에 대해 좋은 느낌이 없습니다. 그러나 비동기 xUnit 테스트를 실행할 때 바로 완료되었습니다. 무슨 일이야?
SuperJMN

2
@SuperJMN : 교체 stuff.Select(x => x.Result);와 함께await Task.WhenAll(stuff)
스티븐 클리어 리

1
@DanielS : 그들은 본질적 으로 동일합니다. 상태 머신, 컨텍스트 캡처, 동기 예외의 동작과 같은 몇 가지 차이점이 있습니다. blog.stephencleary.com/2016/12/eliding-async-await.html
Stephen Cleary

25

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

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

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

.Select(t => t.Result)

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

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

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

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

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

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

참조 : Task.Wait / Task.WhenAll


첫 번째 코드 블록은 실제로 동 기적으로 실행됩니까?
Alexander Derck가

1
예. 결과에 액세스하면 스레드를 차단하는 대기가 발생하기 때문입니다. 반면에 새 작업이 생성되면 기다릴 수 있습니다.
tede24

1
이 질문으로 돌아와서 tasks변수 이름에 대한 당신의 의견을 보면 , 당신은 완전히 옳습니다. 끔찍한 선택, 그들은 즉시 기다리고있는 작업조차 아닙니다. 난 그냥 질문을 그대로 두겠습니다
Alexander Derck

13

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 버전은 작업 모음과 컬렉션 작업을 처리 할 수있는보다 세련된 도구를 제공 할 것입니다.


12

나는이 코드를 사용했다 :

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

5
이것은 단지 IMO 더 애매한 방법으로 기존 기능을 래핑
알렉산더 Derck에게

대안은 var result = await Task.WhenAll (sourceEnumerable.Select (async s => await some function (s, other params))입니다. 작동하지만 LINQy는 아닙니다
Siderite Zackwehdex

두 번째 코드 비트에 언급 Func<TSource, Task<TResult>> method되어 있지 않아야 other params합니까?
matramos

2
추가 매개 변수는 외부에서 실행하려는 함수에 따라 확장 방법의 컨텍스트와 관련이 없습니다.
Siderite Zackwehdex

4
그것은 멋진 확장 방법입니다. 왜 "더 모호한"것으로 여겨지는지 확실하지 않습니다. 동 기적으로 의미가 유사하기 Select()때문에 우아한 기능입니다.
nullPainter

11

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

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

1
Wait실제로 기다리지 않을 때는 메소드를 호출해서는 안됩니다 . 모든 작업이 완료되면 완료되는 작업을 생성합니다. 에뮬레이션 WhenAll하는 Task방법 과 같이 호출하십시오 . 또한 방법이 무의미합니다 async. 전화 WhenAll해서 끝내십시오.
Servy

그냥 원래 메소드를 호출 내 의견으로는 쓸모없는 래퍼의 비트
알렉산더 Derck

@Servy 페어 포인트이지만 특히 이름 옵션이 마음에 들지 않습니다. WhenAll은 그렇지 않은 이벤트처럼 들립니다.
Daryl

3
@AlexanderDerck의 장점은 메소드 체인에 사용할 수 있다는 것입니다.
Daryl

1
@Daryl WhenAll은 평가 된 목록을 반환 하기 때문에 (lazily 평가되지 않음) Task<T[]>반환 유형을 사용하여 이를 나타내는 인수를 만들 수 있습니다 . 기다리면 Linq를 계속 사용할 수 있지만 게으르지 않다는 것을 알립니다.
JAD
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.