Share via


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

작성자 : Rick Anderson

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

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

.NET 4.5를 조합하여 4.5 웹 페이지를 ASP.NET Task 형식의 개체를 반환하는 비동기 메서드를 등록할 수 있습니다. .NET Framework 4에서는 작업이라고 하는 비동기 프로그래밍 개념을 도입했으며 ASP.NET 4.5는 Task를 지원합니다. 작업은 System.Threading.Tasks 네임스페이스의 작업 유형 및 관련 형식으로 표시됩니다. .NET Framework 4.5는 awaitasync 키워드를 사용하여 이 비동기 지원을 기반으로 하여 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는 이러한 차단 호출에 비동기 메서드를 사용하여 더 많은 요청을 서비스할 수 있음을 보여 줍니다.

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

모든 메서드가 비동기적이어야 하는 애플리케이션은 거의 없습니다. 종종 몇 가지 동기 메서드를 비동기 메서드로 변환하면 필요한 작업량에 가장 적합한 효율성이 향상됩니다.

샘플 애플리케이션

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

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

Gizmos 동기 페이지

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

public partial class Gizmos : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        var gizmoService = new GizmoService();
        GizmoGridView.DataSource = gizmoService.GetGizmos();
        GizmoGridView.DataBind();
    }
}

다음 코드는 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 페이지를 보여 줍니다.

웹 API 컨트롤러에 입력한 대로 해당 세부 정보가 포함된 gizmos 테이블을 보여 주는 Gizmos 웹 브라우저 동기화 페이지의 스크린샷

비동기 Gizmos 페이지 만들기

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

ASP.NET 비동기 페이지에는 특성이 "true"로 설정된 Page 지시문 Async 이 포함되어야 합니다. 다음 코드에서는 GizmosAsync.aspx 페이지에 대해 특성이 Async "true"로 설정된 Page 지시문을 보여 줍니다.

<%@ Page Async="true"  Language="C#" AutoEventWireup="true" 
    CodeBehind="GizmosAsync.aspx.cs" Inherits="WebAppAsync.GizmosAsync" %>

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

protected void Page_Load(object sender, EventArgs e)
{
   var gizmoService = new GizmoService();
   GizmoGridView.DataSource = gizmoService.GetGizmos();
   GizmoGridView.DataBind();
}

비동기 버전:

protected void Page_Load(object sender, EventArgs e)
{
    RegisterAsyncTask(new PageAsyncTask(GetGizmosSvcAsync));
}

private async Task GetGizmosSvcAsync()
{
    var gizmoService = new GizmoService();
    GizmosGridView.DataSource = await gizmoService.GetGizmosAsync();
    GizmosGridView.DataBind();
}

페이지를 비동기화할 수 있도록 GizmosAsync 다음과 같은 변경 내용이 적용되었습니다.

  • Page 지시문에는 특성이 Async "true"로 설정되어 있어야 합니다.
  • 메서드는 RegisterAsyncTask 비동기적으로 실행되는 코드를 포함하는 비동기 작업을 등록하는 데 사용됩니다.
  • GetGizmosSvcAsync 메서드는 비동기 키워드(keyword) 표시되어 컴파일러에 본문 부분에 대한 콜백을 생성하고 반환되는 를 자동으로 만들 Task 도록 지시합니다.
  • "비동기"가 비동기 메서드 이름에 추가되었습니다. "비동기"를 추가하는 것은 필요하지 않지만 비동기 메서드를 작성할 때 규칙입니다.
  • GetGizmosSvcAsync 메서드의 반환 형식은 입니다 Task. 의 Task 반환 형식은 진행 중인 작업을 나타내며 메서드의 호출자에게 비동기 작업의 완료를 기다릴 핸들을 제공합니다.
  • await 키워드(keyword) 웹 서비스 호출에 적용되었습니다.
  • 비동기 웹 서비스 API가 호출되었습니다(GetGizmosAsync).

메서드 본문 내에서 GetGizmosSvcAsync 다른 비동기 메서드 가 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 (WebClient webClient = new WebClient())
    {
        return JsonConvert.DeserializeObject<List<Gizmo>>(
            await webClient.DownloadStringTaskAsync(uri)
        );
    }
}

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

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

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

웹 API 컨트롤러에 입력한 대로 해당 세부 정보가 포함된 gizmos 테이블을 보여 주는 Gizmos Async 웹 브라우저 페이지의 스크린샷

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

RegisterAsyncTask Notes

RegisterAsyncTask 연결된 메서드는 PreRender 직후에 실행됩니다.

다음 코드와 같이 비동기 void 페이지 이벤트를 직접 사용하는 경우:

protected async void Page_Load(object sender, EventArgs e) {
    await ...;
    // do work
}

이벤트가 실행되는 시기를 더 이상 완전히 제어할 수 없습니다. 예를 들어 .aspx와 가 모두 이면 입니다. 마스터 정의 Page_Load 이벤트는 하나 또는 둘 다 비동기이므로 실행 순서를 보장할 수 없습니다. 이벤트 처리기에 대해 동일한 확정되지 않은 순서(예: async void Button_Click )가 적용됩니다.

여러 작업을 병렬로 수행

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

protected void Page_Load(object sender, EventArgs e)
{
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();

    var widgetService = new WidgetService();
    var prodService = new ProductService();
    var gizmoService = new GizmoService();

    var pwgVM = new ProdGizWidgetVM(
        widgetService.GetWidgets(),
        prodService.GetProducts(),
        gizmoService.GetGizmos()
       );
    WidgetGridView.DataSource = pwgVM.widgetList;
    WidgetGridView.DataBind();
    ProductGridView.DataSource = pwgVM.prodList;
    ProductGridView.DataBind();
    GizmoGridView.DataSource = pwgVM.gizmoList;
    GizmoGridView.DataBind();

    stopWatch.Stop();
    ElapsedTimeLabel.Text = String.Format("Elapsed time: {0}", 
        stopWatch.Elapsed.Milliseconds / 1000.0);
}

뒤에 있는 비동 PWGasync 기 코드는 다음과 같습니다.

protected void Page_Load(object sender, EventArgs e)
{
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();
    RegisterAsyncTask(new PageAsyncTask(GetPWGsrvAsync));
    stopWatch.Stop();
    ElapsedTimeLabel.Text = String.Format("Elapsed time: {0}",
        stopWatch.Elapsed.Milliseconds / 1000.0);
}

private async Task GetPWGsrvAsync()
{
    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
       );

    WidgetGridView.DataSource = pwgVM.widgetList;
    WidgetGridView.DataBind();
    ProductGridView.DataSource = pwgVM.prodList;
    ProductGridView.DataBind();
    GizmoGridView.DataSource = pwgVM.gizmoList;
    GizmoGridView.DataBind();           
}

다음 이미지는 비동기 PWGasync.aspx 페이지에서 반환된 보기를 보여 줍니다.

위젯, 제품 및 Gizmos 테이블을 보여 주는 비동기 위젯, 제품 및 Gizmos 웹 브라우저 페이지의 스크린샷

취소 토큰 사용

반환되는 Task비동기 메서드는 취소할 수 있습니다. 즉, Page 지시문의 특성과 함께 AsyncTimeout 제공될 때 CancellationToken 매개 변수를 사용합니다. 다음 코드는 시간 제한이 초인 GizmosCancelAsync.aspx 페이지를 보여 줍니다.

<%@ Page  Async="true"  AsyncTimeout="1" 
    Language="C#" AutoEventWireup="true" 
    CodeBehind="GizmosCancelAsync.aspx.cs" 
    Inherits="WebAppAsync.GizmosCancelAsync" %>

다음 코드는 GizmosCancelAsync.aspx.cs 파일을 보여 줍니다.

protected void Page_Load(object sender, EventArgs e)
{
    RegisterAsyncTask(new PageAsyncTask(GetGizmosSvcCancelAsync));
}

private async Task GetGizmosSvcCancelAsync(CancellationToken cancellationToken)
{
    var gizmoService = new GizmoService();
    var gizmoList = await gizmoService.GetGizmosAsync(cancellationToken);
    GizmosGridView.DataSource = gizmoList;
    GizmosGridView.DataBind();
}
private void Page_Error(object sender, EventArgs e)
{
    Exception exc = Server.GetLastError();

    if (exc is TimeoutException)
    {
        // Pass the error on to the Timeout Error page
        Server.Transfer("TimeoutErrorPage.aspx", true);
    }
}

제공된 샘플 애플리케이션에서 GizmosCancelAsync 링크를 선택하면 GizmosCancelAsync.aspx 페이지가 호출되고 비동기 호출의 취소(시간 초과)를 보여 줍니다. 지연 시간이 임의 범위 내에 있기 때문에 시간 초과 오류 메시지를 얻으려면 페이지를 몇 번 새로 고쳐야 할 수 있습니다.

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

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

참가자