모든 서버 측 코드에 대해 ConfigureAwait를 호출하는 모범 사례


561

서버 측 코드 (예 : 일부 ApiController)가 있고 함수가 비동기 적이므로 반환 Task<SomeObject>되는 경우 언제든지 호출하는 함수를 기다리는 것이 가장 좋습니다 ConfigureAwait(false).

스레드 컨텍스트를 원래 스레드 컨텍스트로 다시 전환 할 필요가 없으므로 성능이 뛰어납니다. 그러나 ASP.NET Web Api를 사용하면 요청이 하나의 스레드에 들어오고 함수 ConfigureAwait(false)의 최종 결과를 반환 할 때 다른 스레드에 잠재적으로 영향을 줄 수있는 함수 및 호출 이 대기 ApiController합니다.

아래에서 내가 말하는 것에 대한 예제를 작성했습니다.

public class CustomerController : ApiController
{
    public async Task<Customer> Get(int id)
    {
        // you are on a particular thread here
        var customer = await SomeAsyncFunctionThatGetsCustomer(id).ConfigureAwait(false);

        // now you are on a different thread!  will that cause problems?
        return customer;
    }
}

답변:


628

업데이트 : ASP.NET Core에는가 없습니다SynchronizationContext . ASP.NET Core를 사용하는 경우 사용 여부는 중요 ConfigureAwait(false)하지 않습니다.

ASP.NET "Full"또는 "Classic"등의 경우이 답변의 나머지 부분이 여전히 적용됩니다.

원래 게시물 (비 핵심 ASP.NET 용) :

ASP.NET 팀의 비디오는 ASP.NET에서 사용하는 데 가장 적합한 정보를 제공 async합니다.

스레드 컨텍스트를 원래 스레드 컨텍스트로 다시 전환 할 필요가 없으므로 성능이 뛰어납니다.

"동기화"해야하는 UI 스레드가 하나만있는 UI 응용 프로그램의 경우에도 마찬가지입니다.

ASP.NET에서는 상황이 좀 더 복잡합니다. 때 async메소드 실행을 재개, 상기 ASP.NET 스레드 풀의 스레드를 잡는다. 를 사용하여 컨텍스트 캡처를 비활성화하면 ConfigureAwait(false)스레드가 메소드를 직접 계속 실행합니다. 컨텍스트 캡처를 비활성화하지 않으면 스레드가 요청 컨텍스트를 다시 입력 한 다음 메소드를 계속 실행합니다.

따라서 ConfigureAwait(false)ASP.NET에서 스레드 점프를 저장하지 않습니다. 요청 컨텍스트를 다시 입력하는 것을 저장하지는 않지만 일반적으로 매우 빠릅니다. ConfigureAwait(false) 당신이 요청의 병렬 처리의 작은 금액을하려고 노력하고 있지만 정말 TPL 그 시나리오의 대부분을위한 더 잘 맞는 경우 유용합니다.

그러나 ASP.NET Web Api를 사용하면 요청이 하나의 스레드에 들어오고 ApiController 함수의 최종 결과를 반환 할 때 다른 스레드에 놓일 수있는 기능을 기다리고 ConfigureAwait (false)를 호출하면 .

실제로, 그냥 await할 수 있습니다. async메소드가에 도달 await하면 메소드 는 차단되지만 스레드 는 스레드 풀로 돌아갑니다. 메소드를 계속할 준비가되면 스레드 풀에서 스레드가 걸리고 메소드를 재개하는 데 사용됩니다.

ConfigureAwaitASP.NET 의 유일한 차이점 은 메서드를 다시 시작할 때 해당 스레드가 요청 컨텍스트에 들어가는 지 여부입니다.

MSDN 기사SynchronizationContextasync소개 블로그 게시물 더 많은 배경 정보가 있습니다.


23
스레드 로컬 스토리지는 어떤 컨텍스트 에서도 흐르지 않습니다 . HttpContext.Current는 ASP.NET에 의해 흐릅니다. ASP.NET SynchronizationContext에서는 기본적으로 흐르지 await만,에 의해 흐르지 않습니다 ContinueWith. OTOH, (보안 제한 포함) 실행 컨텍스트는 C #을 통해 CLR에서 언급 한 상황이며, 그것은 되어 모두 흘러 ContinueWithawait(당신이 사용하는 경우에도 ConfigureAwait(false)).
Stephen Cleary

65
C #에 ConfigureAwait (false)에 대한 자국어 지원이 있다면 좋지 않습니까? 'awaitnc'와 같은 것 (컨텍스트를 기다리지 않음). 어디에서나 별도의 메소드 호출을 입력하는 것은 꽤 성가신 일입니다. :)
NathanAldenSr

19
@NathanAldenSr : 꽤 많이 논의되었습니다. 새 키워드의 문제점은 ConfigureAwait실제로 작업 을 기다리는 경우에만 의미가있는 반면 await"대기 가능한" 작업은 수행 한다는 것입니다. 고려되는 다른 옵션은 다음과 같습니다. 기본 동작이 라이브러리에있는 경우 컨텍스트를 삭제해야합니까? 아니면 기본 컨텍스트 동작에 대한 컴파일러 설정이 있습니까? 코드를 읽고 수행하는 것이 더 어려워서 두 가지 모두 거부되었습니다.
Stephen Cleary

10
@AnshulNigam : 컨트롤러 동작에 컨텍스트가 필요한 이유입니다. 그러나 액션이 호출하는 대부분의 메소드는 그렇지 않습니다.
Stephen Cleary

14
@JonathanRoeder : 일반적으로 ASP.NET 에서는 / 를 사용하지 않아야하기 때문에 / 기반 교착 상태 ConfigureAwait(false)를 피할 필요 가 없습니다 . ResultWaitResultWait
Stephen Cleary

131

귀하의 질문에 대한 간단한 답변 : 아니오. 당신은 그런 ConfigureAwait(false)응용 프로그램 수준에서 전화해서는 안됩니다 .

긴 답변의 TL; DR 버전 : 소비자를 모르고 동기화 컨텍스트가 필요하지 않은 라이브러리를 작성하는 경우 (내가 생각하는 라이브러리에는 없어야 함) 항상 사용해야 ConfigureAwait(false)합니다. 그렇지 않으면, 라이브러리 소비자는 비동기 방식을 차단 방식으로 사용하여 교착 상태에 직면 할 수 있습니다. 상황에 따라 다릅니다.

다음은 ConfigureAwait방법 의 중요성에 대한 좀 더 자세한 설명입니다 (내 블로그 게시물의 인용문).

await 키워드를 사용하여 메소드를 기다리는 경우 컴파일러는 사용자를 대신하여 많은 코드를 생성합니다. 이 조치의 목적 중 하나는 UI (또는 기본) 스레드와의 동기화를 처리하는 것입니다. 이 기능의 핵심 구성 요소 SynchronizationContext.Current는 현재 스레드에 대한 동기화 컨텍스트를 얻는 것입니다. SynchronizationContext.Current현재 환경에 따라 채워집니다. GetAwaiter작업 방법을 찾습니다 SynchronizationContext.Current. 현재 동기화 컨텍스트가 널이 아닌 경우 해당 대기자에게 전달 된 연속은 해당 동기화 컨텍스트에 다시 게시됩니다.

새로운 비동기 언어 기능을 사용하는 메소드를 블로킹 방식으로 사용할 때 사용 가능한 SynchronizationContext가있는 경우 교착 상태가 발생합니다. 이러한 메소드를 블로킹 방식으로 사용하는 경우 (Wide with Task 메소드를 기다리거나 Task의 Result 특성에서 직접 결과를 가져 오는 경우) 기본 스레드를 동시에 차단합니다. 결국 작업이 스레드 풀의 해당 메소드 내에서 완료되면 SynchronizationContext.Current사용 가능하고 캡처 되었기 때문에 계속해서 기본 스레드로 다시 게시하도록 호출합니다 . 그러나 여기에 문제가 있습니다 : UI 스레드가 차단되고 교착 상태가 있습니다!

또한 귀하의 질문에 맞는 두 가지 훌륭한 기사가 있습니다.

마지막으로 Lucian Wischik의이 짧은 주제에 대한 짧은 비디오가 있습니다 . 비동기 라이브러리 메소드는 Task.ConfigureAwait (false) 사용을 고려해야 합니다.

도움이 되었기를 바랍니다.


2
"작업의 GetAwaiter 메소드가 SynchronizationContext.Current를 찾습니다. 현재 동기화 컨텍스트가 널이 아닌 경우 해당 대기자에게 전달 된 연속은 해당 동기화 컨텍스트에 다시 게시됩니다." -나는 당신이 Task스택을 걸어서을 얻는다는 인상을 받고 SynchronizationContext있습니다. 을 SynchronizationContext받는 호출하기 전에 잡아되어 Task온 계속하고 코드의 나머지 SynchronizationContext경우 SynchronizationContext.Current는 null이 아니다.
casperOne

1
@casperOne 나는 똑같이 말하려고했습니다.
tugberk

8
클래스 라이브러리 전체 를 쓰지 않고 SynchronizationContext.Current라이브러리가 명확하게 호출 되거나 라이브러리 내에서 호출 되도록하는 것이 호출자의 책임이 아닙니까? Task.Run().ConfigureAwait(false)
binki

1
@binki-반면에 : (1) 아마도 많은 응용 프로그램에서 라이브러리가 사용되는 것 같습니다. 따라서 라이브러리에서 한 번만 노력하면 응용 프로그램을보다 쉽게 ​​만들 수 있습니다. (2) 도서관 저자는 아마도 원래 상황에서 계속할 필요가없는 코드를 작성했다는 것을 알고있을 것이다 .ConfigureAwait(false). 아마도 이것이 기본 동작이라면 라이브러리 작성자에게는 더 쉬울 것입니다. 그러나 라이브러리를 올바르게 작성하는 것이 조금 더 어렵게 만드는 것이 앱을 올바르게 작성하는 것이 조금 더 어려워지는 것보다 낫습니다.
ToolmakerSteve

4
도서관의 저자가 소비자를 왜 묶어야합니까? 소비자가 교착 상태를 원하면 왜 방지해야합니까?
Quarkly

25

ConfigureAwait (false)를 사용하여 찾은 가장 큰 단점은 스레드 문화권이 시스템 기본값으로 되돌아 갔다는 것입니다. 예를 들어 문화를 구성한 경우 ...

<system.web>
    <globalization culture="en-AU" uiCulture="en-AU" />    
    ...

문화권이 en-US로 설정된 서버에서 호스팅하는 경우 ConfigureAwait (false)가 CultureInfo.CurrentCulture가 en-AU를 반환하고 en-US를 얻은 후에 찾을 수 있습니다. 즉

// CultureInfo.CurrentCulture ~ {en-AU}
await xxxx.ConfigureAwait(false);
// CultureInfo.CurrentCulture ~ {en-US}

응용 프로그램이 문화권 별 데이터 형식을 요구하는 작업을 수행하는 경우 ConfigureAwait (false)를 사용할 때이 점에 유의해야합니다.


27
최신 버전의 .NET (4.6 이후로 생각하십니까?) ConfigureAwait(false)은 사용 되더라도 스레드 전체에 문화를 전파 합니다.
Stephen Cleary

1
정보에 대해서 감사드립니다. 우리는 실제로 .net 4.5.2를 사용하고 있습니다
Mick

11

구현에 대한 일반적인 생각이 있습니다 Task.

  1. 작업은 일회용이지만 아직 사용 하지 않아야 합니다 using.
  2. ConfigureAwait4.5에 도입되었습니다. Task4.0에서 소개되었습니다.
  3. .NET 스레드는 항상 컨텍스트를 흐름에 사용하지만 (CLR 서적을 통한 C # 참조) 기본 구현 Task.ContinueWith에서는 b / c가 아니므로 컨텍스트 스위치가 비싸고 기본적으로 꺼져 있습니다.
  4. 문제는 라이브러리 개발자가 클라이언트가 컨텍스트 흐름을 필요로하는지 여부를 신경 쓰지 않아야하므로 컨텍스트 흐름을 결정할지 여부입니다.
  5. [나중에 추가] 정식 답변과 적절한 참조가 없으며 우리가 계속 싸워야한다는 사실은 누군가가 자신의 일을 제대로하지 않았다는 것을 의미합니다.

주제에 대한 몇 가지 게시물이 있지만 Tugberk의 좋은 답변 외에도 내 API는 모든 API를 비동기식으로 설정하고 이상적으로 컨텍스트를 전달해야 한다는 것입니다. 비동기식이므로 라이브러리에서 대기가 수행되지 않기 때문에 교착 상태가 발생하지 않고 컨텍스트가 유지되도록 컨텍스트를 유지하기 때문에 대기 대신에 연속을 사용할 수 있습니다 (예 : HttpContext).

라이브러리가 동기 API를 노출하지만 다른 비동기 API를 사용하는 경우 문제가 발생하므로 코드에서 Wait()/ 를 사용해야 Result합니다.


6
1) Task.Dispose원하는 경우 전화 할 수 있습니다 . 대부분의 시간이 필요하지 않습니다. 2) Task필요하지 않은 TPL의 일부로 .NET 4.0에 도입되었습니다 ConfigureAwait. async추가 되었을 때 그들은 Task새로운 유형을 발명하는 대신 기존 유형 을 재사용했습니다 Future.
Stephen Cleary

6
3) 서로 다른 두 가지 "컨텍스트"유형을 혼동하고 있습니다. CLR을 통해 C #에서 언급 된 "컨텍스트"는 항상 Tasks로 흐릅니다. 에 의해 제어되는 "컨텍스트" ContinueWithSynchronizationContext또는 TaskScheduler입니다. 이러한 다양한 상황 은 Stephen Toub의 블로그에 자세히 설명되어 있습니다.
Stephen Cleary

21
4) 라이브러리 작성자는 각 비동기 메소드가 독립적으로 재개되므로 호출자가 컨텍스트 플로우를 필요로하는지 여부를 신경 쓸 필요가 없습니다. 따라서 호출자가 컨텍스트 플로우를 필요로하는 경우 라이브러리 작성자가 플로우했는지 여부에 관계없이 플로우를 플로우 할 수 있습니다.
Stephen Cleary

1
처음에는 질문에 대답하는 대신 불평하는 것 같습니다. 그리고 .Net에 여러 종류의 컨텍스트가 있다는 것을 제외하고는 "컨텍스트"에 대해 이야기하고 있으며 실제로 어떤 것이 (또는 하나?) 명확하지 않습니다. 그리고 당신이 자신을 혼란스럽게하지 않더라도 (그러나 나는 당신이 생각한다고 생각하지만, Threads 와 함께 흐르는 데 사용되는 컨텍스트 는 없지만 더 이상 그렇지 않다고 생각합니다 ContinueWith()), 이것은 당신의 대답을 읽기가 혼란스럽게 만듭니다.
svick

1
@StephenCleary 예, lib dev는 알 필요가 없습니다. 클라이언트에게 달려 있습니다. 나는 그것을 명확하게 생각했지만 문구가 명확하지 않았습니다.
Aliostad
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.