다음을 통해 공유


ASP.NET MVC 4에서 비동기 메서드 사용

작성자: Rick Anderson

이 자습서에서는 Microsoft Visual Studio의 무료 버전인 Visual Studio Express 2012 for Web을 사용하여 비동기 ASP.NET MVC 웹 애플리케이션을 빌드하는 기본 사항을 설명합니다. Visual Studio 2012를 사용할 수도 있습니다.

github에서 이 자습서에 대한 전체 샘플이 제공됩니다. https://github.com/RickAndMSFT/Async-ASP.NET/

.NET 4.5를 조합하여 ASP.NET MVC 4 Controller 클래스를 사용하면 Task<ActionResult> 형식의 개체를 반환하는 비동기 작업 메서드를 작성할 수 있습니다. .NET Framework 4에서는 태스크라고 하는 비동기 프로그래밍 개념을 도입했으며 MVC 4에서 작업을 지원하는 ASP.NET. 작업은 System.Threading.Tasks 네임스페이스의 작업 유형 및 관련 형식으로 표시됩니다. .NET Framework 4.5는 Await비동기 키워드를 사용하여 이 비동기 지원을 기반으로 하며, Task 개체로 작업하는 것이 이전 비동기 접근 방식보다 훨씬 덜 복잡해집니다. await 키워드(keyword) 코드 조각이 다른 코드 조각을 비동기적으로 기다려야 함을 나타내는 구문 약어입니다. 비동기 키워드(keyword) 메서드를 작업 기반 비동기 메서드로 표시하는 데 사용할 수 있는 힌트를 나타냅니다. await, asyncTask 개체의 조합을 사용하면 .NET 4.5에서 비동기 코드를 훨씬 쉽게 작성할 수 있습니다. 비동기 메서드에 대한 새 모델을 TAP(작업 기반 비동기 패턴)라고 합니다. 이 자습서에서는 awaitasync 키워드와 작업 네임스페이스를 사용하여 비동기 프로그래밍에 대해 잘 알고 있다고 가정합니다.

awaitasync 키워드 및 작업 네임스페이스 사용에 대한 자세한 내용은 다음 참조를 참조하세요.

스레드 풀에서 요청이 처리되는 방법

웹 서버에서 .NET Framework ASP.NET 요청을 서비스하는 데 사용되는 스레드 풀을 유지 관리합니다. 요청이 도착하면 해당 요청을 처리하기 위해 풀에서 하나의 스레드가 디스패치됩니다. 요청이 동기적으로 처리되는 경우 요청이 처리되는 동안 요청을 처리하는 스레드가 사용 중이며 해당 스레드는 다른 요청을 처리할 수 없습니다.

많은 사용 중인 스레드를 수용할 수 있을 만큼 스레드 풀을 충분히 크게 만들 수 있기 때문에 문제가 되지 않을 수 있습니다. 그러나 스레드 풀의 스레드 수는 제한됩니다(.NET 4.5의 기본 최대값은 5,000). 장기 실행 요청의 동시성이 높은 대규모 애플리케이션에서는 사용 가능한 모든 스레드가 사용 중일 수 있습니다. 이러한 상황을 스레드 고갈이라고 합니다. 이 조건에 도달하면 웹 서버는 요청을 큐에 추가합니다. 요청 큐가 가득 차면 웹 서버는 HTTP 503 상태(서버가 너무 없음)으로 요청을 거부합니다. CLR 스레드 풀에는 새 스레드 주입에 대한 제한 사항이 있습니다. 동시성이 버스트되고(즉, 웹 사이트에서 갑자기 많은 수의 요청을 가져올 수 있음) 대기 시간이 긴 백 엔드 호출로 인해 사용 가능한 모든 요청 스레드가 사용 중인 경우 제한된 스레드 삽입 속도로 인해 애플리케이션의 응답이 매우 저하될 수 있습니다. 또한 스레드 풀에 추가된 각 새 스레드에는 오버헤드가 있습니다(예: 스택 메모리 1MB). 스레드 풀이 .NET 4.5 기본 최대값인 5,000개까지 증가하는 높은 대기 시간 호출을 서비스하기 위해 동기 메서드를 사용하는 웹 애플리케이션은 비동기 메서드와 50개의 스레드만 사용하여 동일한 요청을 서비스할 수 있는 애플리케이션보다 약 5GB 더 많은 메모리를 사용합니다. 비동기 작업을 수행할 때 항상 스레드를 사용하는 것은 아닙니다. 예를 들어 비동기 웹 서비스 요청을 수행할 때 ASP.NET 비 동기 메서드 호출과 await 사이의 스레드를 사용하지 않습니다. 스레드 풀을 사용하여 대기 시간이 긴 요청을 서비스하면 메모리 공간이 크고 서버 하드웨어의 사용률이 낮아질 수 있습니다.

비동기 요청 처리

시작 시 많은 수의 동시 요청을 보거나 버스트 로드(동시성이 갑자기 증가하는 경우)가 있는 웹앱에서 웹 서비스를 비동기식으로 호출하면 앱의 응답성이 향상됩니다. 하나의 비동기 요청을 처리하는 시간은 하나의 동기 요청을 처리하는 시간과 동일합니다. 요청이 완료하는 데 2초가 필요한 웹 서비스 호출을 수행하는 경우 요청이 동기적으로 수행되는지 비동기적으로 수행되는지 여부에 관계없이 2초가 걸립니다. 그러나 비동기 호출 중에는 첫 번째 요청이 완료될 때까지 기다리는 동안 스레드가 다른 요청에 응답하지 못하도록 차단되지 않습니다. 따라서 비동기 요청은 장기 실행 작업을 호출하는 많은 동시 요청이 있는 경우 요청 큐 및 스레드 풀 증가를 방지합니다.

동기 또는 비동기 작업 메서드 선택

이 단원에서는 어떤 경우에 동기 또는 비동기 작업 메서드를 사용하는지에 대한 지침을 나열합니다. 이는 지침일 뿐입니다. 각 애플리케이션을 개별적으로 검사하여 비동기 메서드가 성능에 도움이 되는지 여부를 확인합니다.

일반적으로 다음 조건에 동기 메서드를 사용합니다.

  • 작업이 단순하거나 단기 실행 작업인 경우
  • 단순성이 효율성보다 더 중요한 경우
  • 작업이 광범위한 디스크 또는 네트워크 오버헤드를 유발하는 작업이 아닌 주로 CPU 작업인 경우. CPU 관련 작업에 비동기 작업 메서드를 사용하는 경우 이점은 없고 오버헤드만 증가합니다.

일반적으로 다음 조건에 대해 비동기 메서드를 사용합니다.

  • 비동기 메서드를 통해 사용할 수 있는 서비스를 호출하고 .NET 4.5 이상을 사용하고 있습니다.
  • 작업이 CPU 관련 작업이 아닌 네트워크 또는 I/O 관련 작업인 경우
  • 병렬 처리가 코드의 단순성보다 더 중요한 경우
  • 사용자에게 장기 실행 요청을 취소할 수 있는 메커니즘을 제공하려는 경우
  • 스레드 전환의 이점은 컨텍스트 전환 비용보다 큰 경우입니다. 일반적으로 동기 메서드가 작업을 수행하지 않는 동안 ASP.NET 요청 스레드에서 대기하는 경우 메서드를 비동기적으로 만들어야 합니다. 호출을 비동기화하면 웹 서비스 요청이 완료될 때까지 기다리는 동안 ASP.NET 요청 스레드가 중단되지 않습니다.
  • 테스트 결과 차단 작업은 사이트 성능의 병목 현상이며 IIS는 이러한 차단 호출에 비동기 메서드를 사용하여 더 많은 요청을 서비스할 수 있음을 보여 줍니다.

다운로드 가능한 샘플에서는 비동기 작업 메서드를 효율적으로 사용하는 방법을 보여 줍니다. 제공된 샘플은 .NET 4.5를 사용하여 ASP.NET MVC 4에서 비동기 프로그래밍의 간단한 데모를 제공하도록 설계되었습니다. 샘플은 ASP.NET MVC에서 비동기 프로그래밍을 위한 참조 아키텍처가 아닙니다. 샘플 프로그램은 ASP.NET Web API 메서드를 호출하여 Task.Delay를 호출하여 장기 실행 웹 서비스 호출을 시뮬레이션합니다. 대부분의 프로덕션 애플리케이션은 비동기 작업 메서드를 사용할 때 이러한 명백한 이점을 표시하지 않습니다.

모든 작업 메서드가 비동기 방식이어야 하는 응용 프로그램은 극소수입니다. 필요한 작업량을 감안할 때, 몇 개의 동기 작업 메서드를 비동기 메서드로 변환하는 것이 효율성 증대에 최적인 경우가 많습니다.

샘플 애플리케이션

GitHub 사이트에서 샘플 애플리케이션 https://github.com/RickAndMSFT/Async-ASP.NET/ 을 다운로드할 수 있습니다. 리포지토리는 다음 세 개의 프로젝트로 구성됩니다.

  • Mvc4Async: 이 자습서에 사용된 코드를 포함하는 ASP.NET MVC 4 프로젝트입니다. WebAPIpgw 서비스에 대한 Web API 호출을 만듭니다.
  • WebAPIpgw: 컨트롤러를 구현 Products, Gizmos and Widgets 하는 ASP.NET MVC 4 Web API 프로젝트입니다. WebAppAsync 프로젝트 및 Mvc4Async 프로젝트에 대한 데이터를 제공합니다.
  • WebAppAsync: 다른 자습서에서 사용되는 ASP.NET Web Forms 프로젝트입니다.

Gizmos 동기 작업 메서드

다음 코드에서는 gizmos 목록을 표시하는 데 사용되는 동기 작업 메서드를 보여 Gizmos 줍니다. (이 문서에서 gizmo는 가상의 기계 장치입니다.)

public ActionResult Gizmos()
{
    ViewBag.SyncOrAsync = "Synchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", gizmoService.GetGizmos());
}

다음 코드는 gizmo 서비스의 메서드를 보여줍니다 GetGizmos .

public class GizmoService
{
    public async Task<List<Gizmo>> GetGizmosAsync(
        // Implementation removed.
       
    public List<Gizmo> GetGizmos()
    {
        var uri = Util.getServiceUri("Gizmos");
        using (WebClient webClient = new WebClient())
        {
            return JsonConvert.DeserializeObject<List<Gizmo>>(
                webClient.DownloadString(uri)
            );
        }
    }
}

메서드는 GizmoService GetGizmos gizmos 데이터 목록을 반환하는 ASP.NET Web API HTTP 서비스에 URI를 전달합니다. WebAPIpgw 프로젝트에는 Web API gizmos, widgetproduct 컨트롤러의 구현이 포함되어 있습니다.
다음 이미지는 샘플 프로젝트의 gizmos 뷰를 보여 줍니다.

Gizmos

비동기 Gizmos Action 메서드 만들기

이 샘플에서는 새 비동기await 키워드(.NET 4.5 및 Visual Studio 2012에서 사용 가능)를 사용하여 컴파일러가 비동기 프로그래밍에 필요한 복잡한 변환을 유지 관리할 수 있도록 합니다. 컴파일러를 사용하면 C#의 동기 제어 흐름 구문을 사용하여 코드를 작성할 수 있으며, 컴파일러는 스레드 차단을 방지하기 위해 콜백을 사용하는 데 필요한 변환을 자동으로 적용합니다.

다음 코드에서는 동기 메서드와 GizmosAsync 비동기 메서드를 보여 Gizmos 있습니다. 브라우저에서 HTML 5 <mark> 요소를 지원하는 경우 변경 내용 GizmosAsync 이 노란색 강조 표시로 표시됩니다.

public ActionResult Gizmos()
{
    ViewBag.SyncOrAsync = "Synchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", gizmoService.GetGizmos());
}
public async Task<ActionResult> GizmosAsync()
{
    ViewBag.SyncOrAsync = "Asynchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", await gizmoService.GetGizmosAsync());
}

를 비동기식으로 사용할 수 있도록 GizmosAsync 다음과 같은 변경 내용이 적용되었습니다.

  • 메서드는 비동기 키워드(keyword) 표시되어 컴파일러에 본문 부분에 대한 콜백을 생성하고 반환되는 를 Task<ActionResult> 자동으로 만들도록 지시합니다.
  • 메서드 이름에 "Async"가 추가되었습니다. "비동기"를 추가하는 것은 필요하지 않지만 비동기 메서드를 작성할 때는 규칙입니다.
  • 반환 형식이 에서 ActionResultTask<ActionResult>변경되었습니다. 의 Task<ActionResult> 반환 형식은 진행 중인 작업을 나타내며 메서드 호출자에게 비동기 작업의 완료를 기다릴 핸들을 제공합니다. 이 경우 호출자는 웹 서비스입니다. Task<ActionResult> 는 의 결과와 함께 진행 중인 작업을 나타냅니다. ActionResult.
  • await 키워드(keyword) 웹 서비스 호출에 적용되었습니다.
  • 비동기 웹 서비스 API가 호출되었습니다(GetGizmosAsync).

메서드 본문의 GetGizmosAsync 내부에 다른 비동기 메서드가 GetGizmosAsync 호출됩니다. GetGizmosAsync 는 데이터를 사용할 수 있을 때 최종적으로 완료되는 를 즉시 반환 Task<List<Gizmo>> 합니다. gizmo 데이터가 있을 때까지 다른 작업을 수행하지 않으려면 코드가 작업을 대기합니다(await 키워드(keyword) 사용). 비동기 키워드(keyword) 주석이 추가된 메서드에서만 await 키워드(keyword) 사용할 수 있습니다.

await 키워드(keyword) 작업이 완료될 때까지 스레드를 차단하지 않습니다. 작업의 콜백으로 메서드의 나머지 부분을 등록하고 즉시 반환합니다. 대기 중인 작업이 결국 완료되면 해당 콜백을 호출하므로 중단된 바로 그 위치에서 메서드의 실행을 다시 시작합니다. awaitasync 키워드 및 작업 네임스페이스를 사용하는 방법에 대한 자세한 내용은 비동기 참조를 참조하세요.

다음 코드에서는 GetGizmosGetGizmosAsync 메서드를 보여 줍니다.

public List<Gizmo> GetGizmos()
{
    var uri = Util.getServiceUri("Gizmos");
    using (WebClient webClient = new WebClient())
    {
        return JsonConvert.DeserializeObject<List<Gizmo>>(
            webClient.DownloadString(uri)
        );
    }
}
public async Task<List<Gizmo>> GetGizmosAsync()
{
    var uri = Util.getServiceUri("Gizmos");
    using (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri);
        return (await response.Content.ReadAsAsync<List<Gizmo>>());
    }
}

비동기 변경 내용은 위의 GizmosAsync와 유사합니다 .

  • 메서드 시그니처에 비동기 키워드(keyword) 주석이 추가되고 반환 형식이 로 Task<List<Gizmo>>변경되었으며 Async가 메서드 이름에 추가되었습니다.
  • 비동기 HttpClient 클래스는 WebClient 클래스 대신 사용됩니다.
  • await 키워드(keyword) HttpClient 비동기 메서드에 적용되었습니다.

다음 이미지는 비동기 gizmo 뷰를 보여줍니다.

비동기

gizmos 데이터의 브라우저 프레젠테이션은 동기 호출에서 만든 보기와 동일합니다. 유일한 차이점은 비동기 버전이 부하가 많은 상태에서 성능이 더 높을 수 있다는 것입니다.

여러 작업을 병렬로 수행

비동기 작업 메서드는 여러 독립 작업을 수행해야 하는 경우 동기 메서드에 비해 상당한 이점이 있습니다. 제공된 샘플에서 동기 메서드 PWG(Products, Widgets 및 Gizmos의 경우)는 제품, 위젯 및 기즈모 목록을 가져오기 위해 세 번의 웹 서비스 호출 결과를 표시합니다. 이러한 서비스를 제공하는 ASP.NET Web API 프로젝트는 Task.Delay를 사용하여 대기 시간 또는 느린 네트워크 호출을 시뮬레이션합니다. 지연이 500밀리초로 설정되면 비동 PWGasync 기 메서드는 완료하는 데 500밀리초가 조금 넘게 걸리고 동기 PWG 버전은 1,500밀리초가 넘습니다. 동기 PWG 메서드는 다음 코드에 나와 있습니다.

public ActionResult PWG()
{
    ViewBag.SyncType = "Synchronous";
    var widgetService = new WidgetService();
    var prodService = new ProductService();
    var gizmoService = new GizmoService();

    var pwgVM = new ProdGizWidgetVM(
        widgetService.GetWidgets(),
        prodService.GetProducts(),
        gizmoService.GetGizmos()
       );

    return View("PWG", pwgVM);
}

비동 PWGasync 기 메서드는 다음 코드에 나와 있습니다.

public async Task<ActionResult> PWGasync()
{
    ViewBag.SyncType = "Asynchronous";
    var widgetService = new WidgetService();
    var prodService = new ProductService();
    var gizmoService = new GizmoService();

    var widgetTask = widgetService.GetWidgetsAsync();
    var prodTask = prodService.GetProductsAsync();
    var gizmoTask = gizmoService.GetGizmosAsync();

    await Task.WhenAll(widgetTask, prodTask, gizmoTask);

    var pwgVM = new ProdGizWidgetVM(
       widgetTask.Result,
       prodTask.Result,
       gizmoTask.Result
       );

    return View("PWG", pwgVM);
}

다음 이미지는 PWGasync 메서드에서 반환된 뷰를 보여 줍니다.

pwgAsync

취소 토큰 사용

반환되는 Task<ActionResult>비동기 작업 메서드는 취소할 수 있습니다. 즉, AsyncTimeout 특성과 함께 제공될 때 CancellationToken 매개 변수를 사용합니다. 다음 코드에서는 시간 제한이 150밀리초인 메서드를 보여 GizmosCancelAsync 줍니다.

[AsyncTimeout(150)]
[HandleError(ExceptionType = typeof(TimeoutException),
                                    View = "TimeoutError")]
public async Task<ActionResult> GizmosCancelAsync(
                       CancellationToken cancellationToken )
{
    ViewBag.SyncOrAsync = "Asynchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos",
        await gizmoService.GetGizmosAsync(cancellationToken));
}

다음 코드에서는 CancellationToken 매개 변수를 사용하는 GetGizmosAsync 오버로드를 보여 줍니다.

public async Task<List<Gizmo>> GetGizmosAsync(string uri,
    CancellationToken cancelToken = default(CancellationToken))
{
    using (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri, cancelToken);
        return (await response.Content.ReadAsAsync<List<Gizmo>>());
    }
}

제공된 샘플 애플리케이션에서 취소 토큰 데모 링크를 선택하면 메서드가 GizmosCancelAsync 호출되고 비동기 호출의 취소가 표시됩니다.

높은 동시성/높은 대기 시간 웹 서비스 호출을 위한 서버 구성

비동기 웹 애플리케이션의 이점을 실현하려면 기본 서버 구성을 일부 변경해야 할 수 있습니다. 비동기 웹 애플리케이션을 구성하고 스트레스를 테스트할 때 다음 사항에 유의하세요.