C #에서 "return await"의 목적은 무엇입니까?


251

거기에 어떤 과 같은 방법을 쓰는 시나리오 :

public async Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return await DoAnotherThingAsync();
}

이 대신에 :

public Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return DoAnotherThingAsync();
}

말이 될까요?

내부 호출 에서 return await직접 리턴 할 때 구문을 사용하는 이유는 무엇 입니까?Task<T>DoAnotherThingAsync()

return await많은 곳에서 코드가 표시 됩니다. 무언가를 놓친 것 같습니다. 그러나 내가 이해하는 한,이 경우 async / await 키워드를 사용하지 않고 직접 작업을 반환하는 것은 기능적으로 동일합니다. 왜 추가 await레이어의 오버 헤드를 추가 해야합니까?


2
당신이 이것을 보는 유일한 이유는 사람들이 모방으로 배우고 일반적으로 (필요하지 않은 경우) 그들이 찾을 수있는 가장 간단한 해결책을 사용하기 때문입니다. 그래서 사람들은 그 코드를보고, 그 코드를 사용하고, 그것이 작동하는 것을 보았고, 지금부터는 그것을위한 올바른 방법입니다 ... 그 경우에는 기다릴 필요가 없습니다
Fabio Marcolini

7
중요한 차이점은 예외 전파 입니다.
noseratio

1
나는 그것을 이해하지 못하고,이 전체 개념을 전혀 이해할 수 없으며, 이해가되지 않습니다. 메소드에 리턴 유형이 있는지 배운 것에서 IT는 리턴 키워드를 가져야하며 C # 언어의 규칙이 아닙니까?
monstro

@monstro OP의 질문에 return 문이 있습니까?
David Klempfner

답변:


189

하나 부적절한 케이스가 return정상적인 방법과 return await에서 async다르게 동작합니다 방법은 :와 결합 될 때 using(또는보다 일반적으로, 임의의 return awaitA의 try블록).

다음 두 가지 버전의 메소드를 고려하십시오.

Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return foo.DoAnotherThingAsync();
    }
}

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

첫 번째 방법 것이다 객체 즉시로 실제로 완료되기 전에 긴 가능성이 메소드가 복귀. 즉, 첫 번째 버전은 버그 가 많을 수 있으며 ( 너무 빨리 폐기 되므로 ) 두 번째 버전은 제대로 작동합니다.Dispose()FooDoAnotherThingAsync()Foo


4
완성도를 들어, 첫 번째 경우에 당신은 반환해야합니다foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());
ghord

7
@ghord 작동하지 않으면를 Dispose()반환합니다 void. 같은 것이 필요합니다 return foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });. 그러나 두 번째 옵션을 사용할 수있는 이유를 모르겠습니다.
svick

1
@svick 당신 말이 맞아요 { var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }. 유스 케이스는 매우 간단합니다. .NET 4.0을 사용하는 경우 (대부분의 경우) 4.5 앱에서 잘 작동하는 비동기 코드를이 방법으로 작성할 수 있습니다.
ghord

2
@ghord .Net 4.0에 있고 비동기 코드를 작성하려면 Microsoft.Bcl.Async 를 사용해야합니다 . 그리고 코드는 Foo반환이 Task완료된 후에 만 처분 합니다. 필요하지 않은 동시성이 도입되기 때문에 마음에 들지 않습니다.
svick

1
@svick 당신의 코드는 작업이 끝날 때까지 기다립니다. 또한 KB2468871에 대한 종속성으로 인해 Microsoft.Bcl.Async를 사용할 수 없으며 적절한 4.5 비동기 코드와 함께 .NET 4.0 비동기 코드베이스를 사용할 때 충돌이 발생합니다.
ghord

93

필요하지 않은 경우 async(즉, Task직접 반품 할 수 있는 경우)를 사용하지 마십시오 async.

두 가지 비동기 작업을 수행하는 return await경우와 같이 유용한 상황 이 있습니다.

var intermediate = await FirstAsync();
return await SecondAwait(intermediate);

async성능 에 대한 자세한 내용 은 Stephen Toub의 MSDN 기사 및 주제에 대한 비디오참조하십시오 .

업데이트 : 나는 훨씬 더 자세히 블로그 게시물 을 작성했습니다 .


13
await두 번째 경우에 왜 유용한 지에 대한 설명을 추가 할 수 있습니까? 왜 안돼 return SecondAwait(intermediate);?
Matt Smith

2
매트와 같은 질문이 있습니다 return SecondAwait(intermediate);.이 경우 목표를 달성 하지 못 합니까? 나도 return await여기에 중복 이라고 생각 합니다 ...
TX_

23
@MattSmith 컴파일되지 않습니다. 당신이 사용하려는 경우 await첫 번째 줄에, 당신도 두 번째에 사용합니다.
svick

2
@cateyes“병렬로 오버 헤드가 도입 된”의 의미가 확실하지 않지만 async버전은 동기 버전보다 적은 리소스 (스레드)를 사용 합니다.
svick

4
@TomLint 실제로 컴파일되지 않습니다. 의 반환 유형 SecondAwait이`string 인 경우 오류 메시지는 다음과 같습니다. "CS4016 : 이것이 비동기 메소드이므로 리턴 표현식은 'Task <string>'이 아닌 'string'유형이어야합니다."
svick

23

await이전 코드에 다른 것이 있거나 결과를 반환하기 전에 어떤 식 으로든 조작하려는 경우에만 그렇게해야합니다. 발생할 수있는 또 다른 방법은 try/catch예외 처리 방식을 변경하는 것입니다. 그중 아무것도하지 않으면 옳습니다. 메소드를 만드는 오버 헤드를 추가 할 이유가 없습니다 async.


4
Stephen의 답변과 마찬가지로 이전 코드에 다른 대기가있는 경우에도return await자식 호출 작업을 반환하는 대신 필요한지 이해할 수 없습니다 . 설명을 제공해 주시겠습니까?
TX_

10
@TX_ 제거하고 싶다면 async첫 번째 작업을 어떻게 기다리시겠습니까? 대기 async를 사용 하는 것처럼 메소드를 표시해야합니다 . 메소드가로 표시되고 코드 asyncawait이전 코드 await인 경우 올바른 유형이 되려면 두 번째 비동기 작업 이 필요 합니다. 방금 제거한 경우 await반환 값이 올바른 유형이 아니기 때문에 컴파일되지 않습니다. 이 방법은 async결과이므로 항상 작업에 래핑됩니다.
Servy

11
@Noseratio 두 가지를 시도하십시오. 첫 번째 컴파일. 두 번째는 그렇지 않습니다. 오류 메시지가 문제를 알려줍니다. 올바른 유형을 반환하지 않습니다. 에 때 async방법 당신이 작업을 반환하지 않습니다, 당신은 그 포장됩니다 작업의 결과를 반환합니다.
Servy

3
물론 @Servy-당신 말이 맞아요. 후자의 경우 Task<Type>, 명시 적으로 리턴 하는 반면 async리턴하도록 지시합니다 Type(컴파일러 자체가로 바 would Task<Type>).
noseratio

3
@Itsik 확실히, async연속을 명시 적으로 배선하기위한 구문 설탕이다. 당신은하지 않습니다 필요 async 아무것도 할 것이 아니라, 비 사소한 비동기 작업에 대해 바로 수행 할 때 그건 극적으로 보다 쉽게 작업. 예를 들어, 제공 한 코드는 실제로 원하는대로 오류를 전파하지 않으며 훨씬 더 복잡한 상황에서 올바르게 수행하면 훨씬 더 어려워지기 시작합니다. 당신이 결코 필요 하지는 않지만 async, 내가 설명하는 상황은 그것을 사용하기 위해 가치를 더하는 곳입니다.
Servy

17

결과를 기다리는 또 다른 경우는 다음과 같습니다.

async Task<IFoo> GetIFooAsync()
{
    return await GetFooAsync();
}

async Task<Foo> GetFooAsync()
{
    var foo = await CreateFooAsync();
    await foo.InitializeAsync();
    return foo;
}

이 경우 두 방법간에 유형 이 다르고에 직접 할당 할 수 없기 때문에 GetIFooAsync()결과를 기다려야합니다 . 당신이 결과를 기다리고한다면, 그것은 단지하게 되는 것입니다 직접 할당 . 그런 다음 비동기 메소드는 결과를 내부 와 외부로 다시 패키지합니다 .GetFooAsyncTTask<Foo>Task<IFoo>FooIFooTask<IFoo>


1
동의, 이것은 정말로 성가신 일입니다-나는 근본적인 원인 Task<>이 변하지 않는다고 믿습니다 .
StuartLC

7

그렇지 않으면 간단한 "썽크"메소드를 비동기로 만들면 비동기 상태 머신은 메모리에 생성되지만 비동기 상태 머신은 그렇지 않습니다. 비효율적 인 버전을 사용하는 사람들이 종종 비효율적 인 버전을 사용하도록 지시 할 수 있지만, 더 효율적이기 때문에 중단되는 경우 해당 방법이 "반환 / 연속 스택"에 관련되어 있다는 증거가 없습니다. 때로는 중단을 이해하기가 더 어려워집니다.

따라서 perf가 중요하지 않은 경우 (그리고 일반적으로 그렇지 않은 경우) 나중에이 중단을 진단하는 데 도움이되는 비동기 상태 시스템을 사용하여 나중에 중단을 진단하고, 썽크 방식은 시간이 지남에 따라 발전해 왔기 때문에 던져지는 대신 오류가 발생한 작업을 반환해야합니다.


6

return await를 사용하지 않으면 디버깅 중 또는 예외 로그에 인쇄 될 때 스택 추적을 망칠 수 있습니다.

작업을 반환하면 메서드가 목적을 달성했으며 호출 스택에서 벗어났습니다. 사용 return await하면 통화 스택에 그대로 둡니다.

예를 들면 다음과 같습니다.

Await 사용시 호출 스택 : A가 B에서 작업을 기다리는 중 => B가 C에서 작업을 기다리는 중

await를 사용 하지 않을 때의 호출 스택 : A가 B가 리턴 한 C의 태스크를 기다리고 있습니다.


2

이것은 또한 나를 혼란스럽게하며 이전 답변이 실제 질문을 간과했다고 생각합니다.

내부 DoAnotherThingAsync () 호출에서 Task를 직접 반환 할 수있는 경우 return await 구문을 사용하는 이유는 무엇입니까?

때로는 실제로를Task<SomeType>하지만 대부분의 경우 실제로 인스턴스 SomeType, 즉 작업의 결과를 원합니다 .

코드에서 :

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

구문에 익숙하지 않은 사람 (예를 들어, 나)은이 메소드가을 리턴해야한다고 생각할 수도 Task<SomeResult>있지만,로 표시되어 있기 때문에 async실제 리턴 유형은 SomeResult입니다. 을 사용 return foo.DoAnotherThingAsync()하면 컴파일되지 않은 작업이 반환됩니다. 올바른 방법은 작업 결과를 반환하는 것입니다 return await.


1
"실제 반품 유형". 뭐라고? async / await는 반환 유형을 변경하지 않습니다. 귀하의 예 var task = DoSomethingAsync();에서 작업은하지 말고T
Shoe

2
@ 신발 나는 ​​내가 잘 이해하고 있는지 잘 모르겠습니다 async/await. 이해하지만 Task task = DoSomethingAsync(), Something something = await DoSomethingAsync()둘 다 작동합니다. 첫 번째는 적절한 작업을 제공하고 두 번째는 await키워드 로 인해 완료된 후의 결과 를 제공합니다 . 예를 들어 가질 수 있습니다 Task task = DoSomethingAsync(); Something something = await task;.
heltonbiker
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.