174

거기 있니?어떤시나리오 작성 방법은 다음과 같습니다.

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 / 키워드를 기다리지 않고 Task를 직접 반환하는 것이 기능적으로 동일합니다. 추가 오버 헤드를 추가하는 이유await층?


  • 사람들이 모방을 통해 배우고 일반적으로 (필요하지 않으면) 찾을 수있는 가장 간단한 해결책을 사용하기 때문에이 사실을 알 수있는 유일한 이유가 있다고 생각합니다. 사람들은 코드를보고, 코드를 사용하고, 코드가 작동하고, 지금부터는 코드를 사용하여 코드를 작성하는 올바른 방법이라고 생각합니다.이 경우에는 기다릴 필요가 없습니다. - Fabio Marcolini
  • 중요한 차이점이 적어도 하나 있습니다.예외 전파. - noseratio
  • 나는 그것을 전혀 이해하지 못한다. cant는이 전체 개념을 전혀 이해하지 못한다. 메소드에 반환 유형이있는 경우 배웠던 것에서 IT는 return 키워드를 가져야합니다. C #언어의 규칙이 아닙니까? - monstro

6 답변


136

다음 경우에 한 가지 비열한 경우가 있습니다.return정상적인 방법으로return await...에서async메소드가 다르게 동작합니다.using(또는 더 일반적으로return await안에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()그만큼Foo되 자마자DoAnotherThingAsync()메서드가 반환됩니다. 실제로 완료되기 훨씬 전에 발생합니다. 즉, 첫 번째 버전은 버그가있을 수 있습니다 (왜냐하면Foo너무 빨리 처리됩니다.), 두 번째 버전은 정상적으로 작동합니다.


  • 완전성을 위해, 첫 번째 경우에는 반환해야합니다.foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose()); - ghord
  • @ghord 그럴 수 없어,Dispose()보고void. 너는 비슷한 것을 필요로 할거야.return foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });. 하지만 두 번째 옵션을 사용할 수있는 이유는 무엇입니까? - svick
  • @ svick 당신 말이 맞아요. 더 많은 라인을 따라야합니다.{ var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }. 유스 케이스는 매우 간단합니다. .NET 4.0 (대부분)과 같은 경우, 비동기 코드를이 방식으로 작성할 수 있습니다.이 방식으로 4.5 앱에서 제대로 호출됩니다. - ghord
  • @ghord .Net 4.0을 사용하고 있고 비동기 코드를 작성하려는 경우,Microsoft.Bcl.Async. 그리고 당신의 코드는Foo반환 된 후에 만Task완료하지 않습니다. 동시성을 불필요하게 도입하기 때문에 마음에 들지 않습니다. - svick
  • @svick 당신의 코드는 작업이 끝날 때까지 기다린다. 또한 Microsoft.Bcl.Async는 KB2468871의 종속성과 적절한 4.5 비동기 코드로 .NET 4.0 비동기 코드베이스를 사용할 때 충돌로 인해 사용할 수 없습니다. - ghord

68

필요없는 경우async(즉,Task직접) 사용하지 마십시오.async.

몇 가지 상황이 있습니다.return await당신이 가진 것처럼 유용하다.비동기 작업 :

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

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

최신 정보:나는블로그 게시물그것은 훨씬 더 자세하게 설명됩니다.


  • 이유에 대한 설명을 추가 할 수 있습니까?await두 번째 경우에 유용합니까? 왜 안돼?return SecondAwait(intermediate);? - Matt Smith
  • 매트와 같은 질문이 있습니다.return SecondAwait(intermediate);이 경우에도 목표를 달성 할 수 있을까요? 나는 생각한다.return await여기에도 중복됩니다 ... - TX_
  • @MattSmith 컴파일 할 수 없었습니다. 당신이 사용하고 싶다면await첫 번째 줄에서는 두 번째 줄에서도 사용해야합니다. - svick
  • @cateyes "병행하여 도입 된 간접비"가 의미하는 바를 모르겠다.async버전에서 사용할적게동기화 버전보다 리소스 (스레드)가 많습니다. - svick
  • @TomLint실제로는 컴파일되지 않습니다.반환 유형을 다음과 같이 가정합니다.SecondAwaitCS4016 : 이것은 비동기식 메소드이므로, 리턴 표현식은 & # 39; 문자열 & # 39; 유형이어야합니다. & lt; 작업 & lt; 문자열 & gt; & quot; & quot; - svick

22

네가하고 싶은 유일한 이유는 다른 것이 있으면await이전 코드에서, 또는 결과를 조작하기 전에 결과를 조작하는 경우. 그것이 일어날 수있는 또 다른 방법은try/catch예외가 처리되는 방식이 변경됩니다. 네가 맞다면 그 중 하나를 수행하지 않는다면 메소드를 만드는 오버 헤드를 추가 할 이유가 없다.async.


  • Stephen의 대답과 마찬가지로 왜 나는return await자식 호출의 태스크를 반환하는 대신에) 필요하다.이전 코드에서 기다리고있는 다른 것이 있더라도. 설명해 주시겠습니까? - TX_
  • @TX_ 제거하고 싶다면async그러면 첫 번째 과제는 어떻게 기다리고 있을까요? 메소드를 다음과 같이 표시해야합니다.async당신이 사용하고 싶다면어떤기다리고있어. 메소드가 다음과 같이 표시되면async너는await이전 코드에서는await두 번째 비동기 작업은 적절한 유형이어야합니다. 방금 제거한 경우await반환 값이 적절한 유형이 아니므로 컴파일되지 않습니다. 이 방법은async결과는 항상 태스크에 랩핑됩니다. - Servy
  • @Noseratio 두 가지보십시오. 첫 번째 컴파일. 둘째는 안됩니다. 오류 메시지가 문제를 알려줍니다. 적절한 유형을 반환하지 않으실 것입니다. 에있을 때async메서드를 사용하여 작업을 반환하지 않으면 작업의 결과를 반환 한 다음 래핑됩니다. - Servy
  • @Servy, 물론 - 당신이 옳습니다. 후자의 경우 우리는Task<Type>명시 적으로, whileasync돌아 오는 것을 명령하다Type(컴파일러 자체가Task<Type>). - noseratio
  • @Itsik 물론,async연속체를 명시 적으로 연결하기위한 문법적 설탕 일뿐입니다. 너는 몰라.필요한 것 async무엇이든 할 수 있지만, 비범 한 비동기 작업을 수행 할 때는극적으로함께 일하기가 쉽다. 예를 들어, 제공 한 코드가 실제로 원하는대로 오류를 전달하지 못하고 더 복잡한 상황에서 올바르게 수행하면 훨씬 더 어려워집니다. 너는 절대로필요한 것 async, 내가 기술 한 상황은 그것을 사용하는 가치를 부가하는 곳이다. - Servy

12

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

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

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

이 경우,GetIFooAsync()~의 결과를 기다려야한다.GetFooAsync유형이T두 가지 방법이 다르다.Task<Foo>에 직접 할당 할 수 없다.Task<IFoo>. 하지만 결과를 기다리고 있다면Foo어느~이다.에 직접 할당 할 수있는IFoo. 그런 다음 비동기 메서드는 내부에서 결과를 리 패키징합니다.Task<IFoo>그리고 멀리 가라.


3

달리 간단한 "썽크"메서드를 비동기로 만들면 메모리에 비동기 상태 시스템이 만들어지며 비동기식 모드 시스템에서는 그렇지 않습니다. 더 비효율적 인 버전을 사용하는 것은 종종 비효율적 인 버전을 사용하는 것을 가리킬 수 있지만 (사실입니다), 이는 정지 상태 인 경우 해당 메소드가 "리턴 / 연속 스택"과 관련되어 있다는 증거가 없음을 의미합니다. 때로는 걸림쇠를 이해하는 것을 더 어렵게 만듭니다.

그래서 예, 퍼프가 중요하지 않을 때 (그리고 대개는 그렇지 않습니다) 나는이 모든 썽크 메서드에 비동기를 던져서 나중에 비상 정지 상태를 진단 할 수있게 도와줍니다. 썽크 (thunk) 방법은 시간이 지남에 따라 진화 해 나간다. 던진 것이 아니라 잘못 된 작업을 반드시 되돌려 놓을 것이다.


2

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

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

가끔 당신 이요.사실은~을 원한다Task<SomeType>, 그러나 대부분의 경우 실제로 인스턴스를 원한다.SomeType, 즉 작업의 결과입니다.

귀하의 코드에서 :

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

구문에 익숙하지 않은 사람 (예를 들어)은이 메서드가Task<SomeResult>, 표시되어 있기 때문에async, 실제 리턴 유형은 다음과 같습니다.SomeResult. 방금 사용하면return foo.DoAnotherThingAsync(), 컴파일하지 않는 Task를 반환 할 것입니다. 올바른 방법은 작업 결과를 반환하는 것입니다.return await.


  • "실제 반환 유형". 뭐라고? async / 반환 유형을 변경하는 중입니다. 귀하의 예에서var task = DoSomethingAsync();너에게 과제를 주겠다.T - Shoe
  • @ 신발 나는 잘 이해하고 있는지 잘 모르겠다.async/await의회. 나의 이해에,Task task = DoSomethingAsync(), 동안Something something = await DoSomethingAsync()둘 다 작동합니다. 첫 번째는 적절한 작업을 제공하고 두 번째는await키워드를 사용하면결과완료 후 작업에서. 예를 들어 나는Task task = DoSomethingAsync(); Something something = await task;. - heltonbiker

연결된 질문


관련된 질문

최근 질문