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

서버 측 코드 (예 : 일부 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;
    }
}



답변

업데이트 : 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소개 블로그 게시물 더 많은 배경 정보가 있습니다.


답변

귀하의 질문에 대한 간단한 답변 : 아니오. 당신은 그런 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) 사용을 고려해야 합니다.

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


답변

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)를 사용할 때이 점에 유의해야합니다.


답변

구현에 대한 일반적인 생각이 있습니다 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합니다.


답변