왜 메소드를 '대기'한 다음 즉시 리턴 값을 조사합니까?


24

에서 이 MSDN 문서 , 다음 예제 코드는 (약간 간결 편집) 제공됩니다

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    Department department = await db.Departments.FindAsync(id);

    if (department == null)
    {
        return HttpNotFound();
    }

    return View(department);
}

FindAsync메소드 Department는 ID 로 객체를 검색 하고를 반환합니다 Task<Department>. 그런 다음 부서가 즉시 null인지 확인합니다. 내가 이해하는 것처럼이 방법으로 Task 값을 요청하면 대기중인 메소드의 값이 반환 될 때까지 코드 실행차단되어 효과적으로 동기 호출이됩니다.

왜 이렇게 하시겠습니까? Find(id)어쨌든 즉시 차단하려는 경우 동기 메소드를 호출하는 것이 더 간단하지 않습니까?


구현과 관련이있을 수 있습니다. ... else return null;그런 다음 메소드가 실제로 요청한 부서를 찾았는지 확인해야합니다.
Jeremy Kato

나는 asp.net에서 아무것도 보지 못했지만, destop 앱에서는 이런 식으로 그렇게함으로써 ui를
멈추지

다음은 디자이너의 기다림 개념을 설명하는 링크입니다. msdn.microsoft.com/en-us/magazine/hh456401.aspx
Jon Raynor

스레드 접점 스위치 속도가 느려지거나 많은 스레드 스택에서 메모리 사용이 문제인 경우 Await는 ASP.NET에 대해서만 고려해야합니다.
Ian

답변:


24

내가 이해하는 것처럼이 방법으로 Task 값을 요청하면 대기중인 메소드의 값이 반환 될 때까지 코드 실행이 차단되어 효과적으로 동기 호출이됩니다.

좀 빠지는.

호출 await db.Departments.FindAsync(id)하면 작업이 전송되고 다른 작업에서 사용하기 위해 현재 스레드가 풀로 반환됩니다. 실행 흐름은 차단되지만 (사실을 department올바르게 이해하면 바로 사용 하지 않더라도) 스레드 외부는 작업이 기계 외부에서 완료되기를 기다리는 동안 다른 것들에 의해 자유롭게 사용될 수 있습니다 (그리고 이벤트 또는 완료 포트에 의해 시그널링 됨).

호출 d.Departments.Find(id)하면 스레드가 거기에 앉아서 응답을 기다립니다. 대부분의 처리가 DB에서 수행되고 있습니다.

디스크 바인딩시 CPU 리소스를 효과적으로 비 웁니다.


2
그러나 모든 await것이 동일한 스레드에서 연속으로 나머지 메소드에 서명하는 것 (예외가 있습니다. 일부 비동기 메소드는 자체 스레드를 회전시킵니다) 또는 async동일한 스레드에서 연속으로 메소드에 서명 하고 실행할 나머지 코드 (보시다시피, async작동 방식 에 대해서는 명확하지 않습니다 ). 당신이 묘사 한 것은보다 정교한 형태의 소리입니다Thread.Sleep(untilReturnValueAvailable)
Robert Harvey

1
@RobertHarvey-연속으로 지정하지만 처리 할 작업 (및 연속)을 보낸 후에는 실행할 것이 없습니다. ( ConfigureAwaitiirc 를 통해 ) 지정하지 않으면 동일한 스레드에 있다고 보장되지 않습니다 .
Telastyn

1
내 대답을 참조하십시오 ... 연속은 기본적으로 원래 스레드로 다시 정렬됩니다.
Michael Brown

2
내가 여기서 잃어버린 것을 본 것 같아요. 이것이 이점을 제공하기 위해서는 ASP.NET MVC 프레임 워크가에 await대한 호출을 해야합니다 public async Task<ActionResult> Details(int? id). 그렇지 않으면 원래 통화가 차단 department == null되어 해결을 기다 립니다.
Robert Harvey

2
@RobertHarvey 전화가 await ..."되돌아 올" 때까지 FindAsync. 그게 기다리고 있습니다. 코드를 기다리게하기 때문에 대기라고합니다. (그러나 현재 스레드가 사물을 기다리게하는
것과는 다릅니다.

17

나는 예제 중 어느 것도 작업을 기다리기 전에 몇 줄을 기다리는 것이 가능한 방법을 보여주지 않는 것이 정말 싫어. 이걸 고려하세요.

Foo foo = await getFoo();
Bar bar = await getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(foo, bar);

이것은 예제에서 권장하는 종류의 코드이며 맞습니다. 이 부분에는 의미가 없습니다. 메인 스레드가 UI 입력에 대한 응답과 같은 다른 작업을 수행 할 수는 있지만 async / await의 진정한 힘은 잠재적으로 오래 실행되는 작업이 완료되기를 기다리는 동안 다른 작업을 쉽게 계속 수행 할 수 있다는 것입니다. 위 코드는 Foo & Bar가 표시 될 때까지“차단”되어 인쇄 라인 실행을 기다립니다. 기다릴 필요는 없습니다. 기다리는 동안 처리 할 수 ​​있습니다.

Task<Foo> foo = getFoo();
Task<Bar> bar = getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(await foo, await bar);

이제 재 작성된 코드를 사용하여 멈추지 않고 값을 기다릴 필요가 없습니다. 나는 항상 이런 종류의 기회를 찾고 있습니다. 기다릴 때 현명하게 행동하면 성능이 크게 향상 될 수 있습니다. 요즘에는 여러 개의 코어가 있습니다.


1
흠, 그것은 코어 / 스레드와 비동기 호출을 올바르게 사용하는 것에 관한 것이 아닙니다.
중복 제거기

@Deduplicator가 잘못되었습니다. 그렇기 때문에 답변에“차단”을 인용했습니다. 스레드를 언급하지 않고이 물건에 대해 말하기는 어렵지만 여전히 인정하면서 멀티 스레딩이있을 수도 있고 아닐 수도 있습니다.
RubberDuck

다른 메소드 호출을 기다리는 데주의를 기울이는 것이 좋습니다. 때로는 컴파일러가 이것을 기다리고 오해하고 정말 이상한 예외가 발생합니다. 모든 async / await는 구문 설탕 일 뿐이므로, 생성 된 코드의 모양을 sharplab.io로 확인할 수 있습니다. 나는 이것을 여러 번 관찰했으며 이제 결과가 필요한 호출 위의 한 줄만 기다립니다 ... 두려운 두통이 필요하지 않습니다.
퇴거

"이것에는 의미가 없습니다." -이것에 많은 의미가 있습니다. "다른 일을 계속 수행"하는 것이 동일한 방법으로 적용되는 경우는 그리 일반적이지 않습니다. await스레드가 대신 완전히 다른 일을하도록하는 것이 훨씬 일반적입니다 .
Sebastian Redl

@SebastianRedl에서“이것은 조금 의미가 있습니다.”라고 말했을 때, 나는 당신이 분명히 두 가지 작업을 모두 실행, 대기, 실행, 대기 대신 병렬로 실행할 수있는 경우를 의미했습니다. 생각보다 훨씬 일반적입니다. 코드베이스를 살펴보면 기회를 찾을 수 있습니다.
RubberDuck

6

여기에 뒤에서 더 많은 일이 발생합니다. Async / Await는 구문 설탕입니다. 먼저 FindAsync 함수의 서명을보십시오. 작업을 반환합니다. 이미 키워드의 마법을보고 있으면 해당 작업을 부서로 개봉합니다.

호출 기능이 차단되지 않습니다. 부서와 할당 키워드 뒤에 오는 모든 항목이 클로저로 묶이고 Task.ContinueWith 메서드에 전달 된 모든 의도와 목적으로 FindAsync 함수가 자동으로 다른 스레드에서 실행됩니다.

물론 작업이 원래 스레드로 마샬링되므로 (백그라운드 작업을 수행 할 때 UI 동기화에 대해 더 이상 걱정할 필요가 없음) 호출 함수가 비동기 ( 그리고 비동기 적으로 호출되는) 스택에서 동일한 일이 발생합니다.

따라서 발생하는 일은 함정없이 비동기 작업의 마법을 얻는 것입니다.


1

아니요 즉시 반환되지 않습니다. 대기는 메소드 호출을 비동기로 만듭니다. FindAsync가 호출되면 Details 메서드는 완료되지 않은 작업과 함께 반환됩니다. FindAsync가 끝나면 결과를 부서 변수로 반환하고 나머지 Details 메서드를 다시 시작합니다.


전혀 차단이 없습니다. 비동기 메서드가 이미 완료 될 때까지는 부서 == null에 도달하지 않습니다.
Steve

1
async await일반적으로 새 스레드를 생성하지 않으며, 그렇게해도 해당 부서 값이 널인지 확인하기 위해 대기해야합니다.
Robert Harvey

2
기본적으로 당신이 말하는 것은 이것이 전혀 유익하지 않기 위해서는에 대한 호출 public async Task<ActionResult> 도 반드시해야만한다는 것 await입니다.
Robert Harvey

2
@RobertHarvey 네, 아이디어가 있습니다. 비동기 / 대기는 본질적으로 바이러스 성입니다. 하나의 함수를 "기다리는"경우이를 호출하는 함수도 기다려야합니다. await혼합해서는 안 .Wait()또는 .Result이 교착 상태의 원인이 될 수 있습니다. 비동기 / 대기 체인은 일반적으로 async void서명이 있는 함수에서 끝나며 , 주로 이벤트 핸들러 또는 UI 요소가 직접 호출하는 함수에 사용됩니다.
KChaloux

1
@Steve- " ... 자체 스레드에있을 것입니다. "아니요, 읽기 작업은 여전히 ​​스레드가 아니며 비동기가 병렬이 아닙니다 . 여기에는 새 스레드가 작성되지 않았 음을 나타내는 실제 출력이 포함됩니다.
ToolmakerSteve

1

"비동기"를 계약이라고 생각하고 싶습니다. "필요한 경우 비동기 적으로 실행할 수 있지만 다른 동기 함수처럼 호출 할 수도 있습니다"라는 계약입니다.

즉, 한 개발자가 함수를 만들고 일부 디자인 결정으로 인해 여러 함수를 "비동기"로 만들거나 표시했습니다. 함수의 호출자 / 소비자는 원하는대로 함수를 사용할 수 있습니다. 당신이 말한 것처럼 함수 호출 직전에 await를 호출하고 기다릴 수 있습니다.이 방법으로 동기 함수처럼 취급했지만 원하는 경우 다음과 같이 기다리지 않고 호출 할 수 있습니다

Task<Department> deptTask = db.Departments.FindAsync(id);

예를 들어, 당신이 호출하는 함수의 10 줄을

Department d = await deptTask;

따라서이를 비동기 함수로 취급합니다.

그것은 당신에게 달려 있습니다.


1
"메시지를 비동기 적으로 처리 할 수있는 권한을 보유하고 있습니다. 결과를 얻기 위해 이것을 사용하십시오."
중복 제거기

-1

"어쨌든 즉시 차단하려는 경우"대답은 "예"입니다. 빠른 응답이 필요한 경우에만 대기 / 비동기 적으로 이해하십시오. 예를 들어 UI 스레드가 실제로 비동기 메서드가되면 UI 스레드가 반환되어 버튼 클릭을 계속 듣습니다. 반면 "await"아래의 코드는 다른 스레드에 의해 수행되어 결과를 얻습니다.


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