[C#] ASP.NET MVC에서 비동기 작업을 수행하려면 .NET 4의 ThreadPool에서 스레드를 사용하십시오.

이 질문 후에 ASP.NET MVC에서 비동기 작업을 사용할 때 편안합니다. 그래서 나는 그것에 대해 두 개의 블로그 게시물을 썼습니다.

ASP.NET MVC의 비동기 작업에 대한 오해가 너무 많습니다.

항상이 문장을 듣습니다. 작업이 비동기 적으로 실행되면 응용 프로그램의 확장 성이 향상 될 수 있습니다

그리고 이런 종류의 문장도 많이 들었습니다. 대량의 트래픽이있는 경우 쿼리를 비동기 적으로 수행하지 않는 것이 좋습니다. 하나의 요청을 처리하기 위해 2 개의 추가 스레드를 소비하면 다른 들어오는 요청에서 리소스가 제거됩니다.

나는이 두 문장이 일치하지 않는다고 생각합니다.

스레드 풀이 ASP.NET에서 작동하는 방법에 대한 많은 정보는 없지만 스레드 풀의 크기가 스레드에 대해 제한된다는 것을 알고 있습니다. 따라서 두 번째 문장은이 문제와 관련이 있습니다.

그리고 ASP.NET MVC의 비동기 작업이 .NET 4의 ThreadPool에서 스레드를 사용하는지 알고 싶습니다.

예를 들어 AsyncController를 구현할 때 앱은 어떻게 구성됩니까? 트래픽이 많은 경우 AsyncController를 구현하는 것이 좋습니다.

ASP.NET MVC 3 (NET 4)의 비 동시성에 대한 거래를 설명 할 수있는 사람이 있습니까?

편집하다:

나는이 문서를 거의 수백 번 읽었으며 주요 거래를 이해하지만 여전히 일관성이없는 의견이 많기 때문에 혼란 스럽습니다.

ASP.NET MVC에서 비동기 컨트롤러 사용

편집하다:

아래와 같은 컨트롤러 동작이 있다고 가정 해 봅시다 ( AsyncController그러나 구현은 아님).

public ViewResult Index() {

    Task.Factory.StartNew(() => {
        //Do an advanced looging here which takes a while
    });

    return View();
}

당신이 여기에서 볼 수 있듯이, 나는 작업을 시작하고 잊어 버렸습니다. 그런 다음 완료되기를 기다리지 않고 즉시 돌아옵니다.

이 경우 스레드 풀의 스레드를 사용해야합니까? 그렇다면 완료 후 해당 스레드는 어떻게됩니까? 합니까이 GC오고가 완료 직후 청소?

편집하다:

@Darin의 답변을 위해 데이터베이스와 통신하는 비동기 코드 샘플이 있습니다.

public class FooController : AsyncController {

    //EF 4.2 DbContext instance
    MyContext _context = new MyContext();

    public void IndexAsync() {

        AsyncManager.OutstandingOperations.Increment(3);

        Task<IEnumerable<Foo>>.Factory.StartNew(() => {

           return
                _context.Foos;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foos"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<Bars>>.Factory.StartNew(() => {

           return
                _context.Bars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["bars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<FooBar>>.Factory.StartNew(() => {

           return
                _context.FooBars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foobars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });
    }

    public ViewResult IndexCompleted(
        IEnumerable<Foo> foos,
        IEnumerable<Bar> bars,
        IEnumerable<FooBar> foobars) {

        //Do the regular stuff and return

    }
}



답변

여기의 우수한 기사 당신이 더 나은 (비동기 컨트롤러는 기본적으로 무엇을 나타내는 지입니다) ASP.NET에서 비동기 처리를 이해하는 읽기 추천 할 것입니다.

먼저 표준 동기 동작을 고려해 보겠습니다.

public ActionResult Index()
{
    // some processing
    return View();
}

이 조치를 요청하면 스레드 풀에서 스레드가 작성되고이 스레드에서이 조치의 본문이 실행됩니다. 따라서이 조치 내부의 처리가 느리면 전체 처리에 대해이 스레드를 차단하므로이 스레드를 재사용하여 다른 요청을 처리 할 수 ​​없습니다. 요청 실행이 끝나면 스레드가 스레드 풀로 리턴됩니다.

이제 비동기 패턴의 예를 보자.

public void IndexAsync()
{
    // perform some processing
}

public ActionResult IndexCompleted(object result)
{
    return View();
}

요청이 인덱스 조치로 전송되면 스레드 풀에서 스레드가 작성되고 IndexAsync메소드 본문 이 실행됩니다. 이 메소드의 본문이 실행을 마치면 스레드가 스레드 풀로 리턴됩니다. 그런 다음 표준을 사용하여 AsyncManager.OutstandingOperations비동기 작업의 완료를 알리면 스레드 풀에서 다른 스레드를 가져 IndexCompleted와서 작업 본문 이 실행되고 결과가 클라이언트에 렌더링됩니다.

따라서이 패턴에서 볼 수있는 것은 단일 클라이언트 HTTP 요청이 두 개의 다른 스레드에서 실행될 수 있다는 것입니다.

이제 흥미로운 부분이 IndexAsync메서드 내부에서 발생합니다 . 그 안에 차단 작업이있는 경우 작업자 스레드를 차단하기 때문에 비동기 컨트롤러의 전체 목적을 완전히 낭비하는 것입니다 (이 작업의 본문은 스레드 풀에서 가져온 스레드에서 실행됨을 기억하십시오).

그렇다면 언제 비동기 컨트롤러를 활용할 수 있습니까?

IMHO는 I / O 집약적 인 작업 (예 : 데이터베이스 및 원격 서비스에 대한 네트워크 호출)이있을 때 가장 많이 얻을 수 있습니다. CPU 집약적 인 작업이있는 경우 비동기 작업으로 많은 이점을 얻지 못합니다.

그렇다면 왜 I / O 집약적 운영의 이점을 얻을 수 있습니까? I / O 완료 포트를 사용할 수 있기 때문 입니다. IOCP는 전체 작업을 실행하는 동안 서버에서 스레드 또는 리소스를 사용하지 않기 때문에 매우 강력합니다.

그들은 어떻게 작동합니까?

WebClient.DownloadStringAsync 메소드를 사용하여 원격 웹 페이지의 컨텐츠를 다운로드한다고 가정하십시오 . 운영 체제 내에서 IOCP를 등록하고 즉시 돌아 오는이 메소드를 호출합니다. 전체 요청을 처리하는 동안 서버에서 스레드가 소비되지 않습니다. 모든 것은 원격 서버에서 발생합니다. 이 작업에는 많은 시간이 걸릴 수 있지만 작업자 스레드를 위험에 빠뜨리지 않으므로 신경 쓰지 않아도됩니다. 응답이 수신되면 IOCP에 신호가 전달되고 스레드 풀에서 스레드가 생성되고이 스레드에서 콜백이 실행됩니다. 그러나 보시다시피, 전체 프로세스 중에 스레드를 독점하지 않았습니다.

FileStream.BeginRead, SqlCommand.BeginExecute, …와 같은 메소드에서도 마찬가지입니다.

여러 데이터베이스 호출을 병렬화하는 것은 어떻습니까? 4 개의 블로킹 데이터베이스 호출을 순서대로 수행하는 동기 제어기 조치가 있다고 가정하십시오. 각 데이터베이스 호출에 200ms가 걸리면 컨트롤러 작업을 실행하는 데 약 800ms가 걸린다는 것을 쉽게 계산할 수 있습니다.

이러한 호출을 순차적으로 실행할 필요가 없다면 병렬화하면 성능이 향상됩니까?

큰 질문인데 대답하기 쉽지 않습니다. 아마 그렇습니다, 아마 그렇습니다. 데이터베이스 호출을 구현하는 방법에 전적으로 의존합니다. 앞에서 설명한대로 비동기 컨트롤러 및 I / O 완료 포트를 사용하면 작업자 스레드를 독점하지 않기 때문에이 컨트롤러 작업 및 다른 작업의 성능을 향상시킵니다.

반면에 스레드 풀의 스레드에서 차단 데이터베이스 호출을 수행하여 제대로 구현하지 않으면 기본적 으로이 작업의 총 실행 시간을 약 200ms로 줄이지 만 작업자 스레드 4 개를 소비했을 것입니다. 풀에서 스레드를 처리하여 처리 할 수 ​​없어서 다른 요청의 성능이 저하되었을 수 있습니다.

따라서 매우 어렵고 응용 프로그램에서 광범위한 테스트를 수행 할 준비가되지 않았다면 비동기 컨트롤러를 구현하지 마십시오. 이점보다 더 많은 피해를 입을 수 있습니다. 예를 들어 표준 동기 컨트롤러 작업이 광범위한로드 테스트 및 측정을 수행 한 후 응용 프로그램에 병목 현상이 있음을 확인했습니다.

이제 당신의 예를 생각해 봅시다 :

public ViewResult Index() {

    Task.Factory.StartNew(() => {
        //Do an advanced looging here which takes a while
    });

    return View();
}

인덱스 조치에 대한 요청이 수신되면 스레드가 스레드 풀에서 스레드를 가져 와서 본문을 실행하지만 본문은 TPL을 사용하여 새 태스크 만 스케줄합니다 . 따라서 조치 실행이 종료되고 스레드가 스레드 풀로 리턴됩니다. 이를 제외하고 TPL 은 스레드 풀의 스레드를 사용하여 처리를 수행합니다. 따라서 원래 스레드가 스레드 풀로 리턴 된 경우에도이 풀에서 다른 스레드를 작성하여 태스크 본문을 실행했습니다. 그래서 당신은 당신의 소중한 수영장에서 2 실을 위태롭게했습니다.

이제 다음을 고려하십시오.

public ViewResult Index() {

    new Thread(() => {
        //Do an advanced looging here which takes a while
    }).Start();

    return View();
}

이 경우 스레드를 수동으로 생성합니다. 이 경우 인덱스 작업 본문 실행에 약간의 시간이 소요될 수 있습니다 (기존 풀에서 스레드를 생성하는 것보다 새 스레드를 생성하는 것이 더 비싸기 때문에). 그러나 고급 로깅 ​​작업의 실행은 풀의 일부가 아닌 스레드에서 수행됩니다. 따라서 우리는 다른 요청을 처리하기 위해 무료로 남아있는 풀의 스레드를 위태롭게하지 않습니다.


답변

예-모든 스레드가 스레드 풀에서 나옵니다. 요청이 새 스레드에 들어올 때 MVC 앱은 이미 다중 스레드입니다. 풀에서 가져 와서 요청을 처리하는 데 사용됩니다. 요청이 완전히 서비스되고 완료 될 때까지 해당 스레드는 (다른 요청에서) ‘잠금’됩니다. 풀에 사용 가능한 스레드가없는 경우 요청은 사용 가능한 스레드가 될 때까지 기다려야합니다.

비동기 컨트롤러가있는 경우 여전히 풀에서 스레드를 얻지 만 요청을 처리하는 동안 스레드가 포기되고 무언가가 발생할 때까지 (및 해당 스레드가 다른 요청에 제공 될 수 있음) 원래 요청에 스레드가 필요한 경우 다시 수영장에서 하나를 가져옵니다.

차이점은 스레드가 무언가로부터 응답을 기다리는 많은 장기 실행 요청이있는 경우 풀에서 스레드가 부족하여 기본 요청까지 처리 할 수 ​​있다는 것입니다. 비동기 컨트롤러가있는 경우 더 이상 스레드가 없지만 대기중인 스레드는 풀로 반환되어 다른 요청을 처리 할 수 ​​있습니다.

거의 실생활의 예 … 버스에 점점처럼 생각, 거기는 처음에 도착 지불에 도착을 기다리고 오명이고 (운전자가 자신의 요청을 서비스), 당신이 얻을 앉는다 (드라이버가 서비스하는 귀하의 요청)하지만 돈을 찾을 수 없습니다. 당신이 당신의 주머니에서 비틀 거리면서 운전자가 당신을 포기하고 다음 두 사람을 (요청에 응함) 얻습니다. 당신은 끝났지 만 당신이 봉사하는 절반의 길에있는 동안 세 번째와 네 번째 사람들이 봉사되었습니다. 이것은 드라이버가 수영장에서 유일하고 스레드이며 승객이 요청임을 의미합니다. 두 명의 드라이버가 있으면 어떻게 작동하는지 작성하기가 너무 복잡했지만 상상할 수 있습니다 …

비동기 컨트롤러가 없다면, 버스 운전사가 일을하지 않는 동안 뒤에 승객들은 돈을 찾는 동안 나이를 기다려야 할 것입니다.

결론적으로 많은 사람들이 돈이 어디에 있는지 알지 못한다면 (즉, 운전자가 요청한 것에 응답하는 데 오랜 시간이 걸리는 경우) 비동기 컨트롤러는 요청의 처리량을 도와서 일부 프로세스의 속도를 높일 수 있습니다. 인공 지능 컨트롤러가 없으면 모든 사람이 앞 사람이 완전히 다룰 때까지 기다립니다. 그러나 MVC에서는 단일 버스에 많은 버스 드라이버가 있으므로 비동기가 자동 선택이 아니라는 것을 잊지 마십시오.


답변

여기에는 두 가지 개념이 있습니다. 우선 코드를 병렬로 실행하여 사용자가 기다리지 않도록 다른 스레드에서 코드를 더 빠르게 실행하거나 예약 할 수 있습니다. 당신이 가진 예

public ViewResult Index() {

    Task.Factory.StartNew(() => {
        //Do an advanced looging here which takes a while
    });

    return View();
}

두 번째 카테고리에 속합니다. 사용자는 더 빠른 응답을 얻을 수 있지만 동일한 작업을 수행하고 스레딩을 처리해야하기 때문에 서버의 총 워크로드가 더 높습니다.

이것의 또 다른 예는 다음과 같습니다.

public ViewResult Index() {

    Task.Factory.StartNew(() => {
        //Make async web request to twitter with WebClient.DownloadString()
    });

    Task.Factory.StartNew(() => {
        //Make async web request to facebook with WebClient.DownloadString()
    });


    //wait for both to be ready and merge the results

    return View();
}

요청이 병렬로 실행되기 때문에 사용자는 직렬로 수행되는 것처럼 기다릴 필요가 없습니다. 그러나 스레드 대기 상태에있는 동안 많은 스레드에서 코드를 실행하기 때문에 직렬로 실행하는 경우보다 여기에서 더 많은 리소스를 사용한다는 것을 알고 있어야합니다.

이것은 클라이언트 시나리오에서 완벽하게 좋습니다. 그리고 새로운 작업에서 동기식 장기 실행 코드를 래핑 (다른 스레드에서 실행)하는 것도 UI를 신속하게 처리하거나 병렬화하여 유지하는 것이 일반적입니다. 스레드는 여전히 전체 기간 동안 사용됩니다. 로드가 많은 서버에서는 실제로 더 많은 리소스를 사용하기 때문에 역효과가 발생할 수 있습니다. 사람들이 경고 한 내용입니다.

MVC의 비동기 컨트롤러는 또 다른 목표가 있습니다. 여기서 요점은 스레드가 아무것도하지 않는 주위에 앉지 않도록하는 것입니다 (확장 성을 해칠 수 있음). 호출하는 API에 비동기 메소드가 있는지 여부 만 중요합니다. WebClient.DowloadStringAsync ()와 같습니다.

요점은 웹 요청이 완료 될 때까지 스레드가 반환되어 새 요청을 처리하도록 할 수 있다는 것입니다. 웹 요청은 동일한 또는 새 스레드를 가져 와서 요청을 완료하는 콜백을 호출합니다.

비동기와 병렬의 차이점을 이해하기를 바랍니다. 병렬 코드는 스레드가 둘러 앉아 결과를 기다리는 코드로 생각하십시오. 비동기 코드는 코드가 완료되면 알림을 받고 다시 작업 할 수있는 코드이지만 스레드는 다른 작업을 수행 할 수 있습니다.


답변

작업이 비동기 적으로 실행 되는 경우 추가 작업을 처리하는 데 사용할 수있는 리소스가있는 경우에만 응용 프로그램 확장 할 수 있습니다 .

비동기 작업은 기존 작업이 진행 중이므로 작업을 차단하지 않습니다. ASP.NET에는 여러 요청을 나란히 실행할 수있는 비동기 모델이 있습니다. 요청을 대기열에 넣고 FIFO를 처리하는 것이 가능하지만, 수백 개의 요청이 대기열에 있고 각 요청이 처리하는 데 100ms가 걸리면 확장 성이 떨어집니다.

많은 양의 트래픽 있는 경우 요청을 처리 할 추가 리소스가 없을 수 있으므로 쿼리를 비동기 적으로 수행하지 않는 것이 좋습니다 . 여분의 리소스가 없으면 요청이 대기열에 들어가거나 기하 급수적으로 더 길거나 완전히 실패합니다.이 경우 비동기 오버 헤드 (뮤텍스 및 컨텍스트 전환 작업)가 아무 것도 제공하지 않습니다.

ASP.NET은 선택의 여지가 없습니다. 비동기 모델을 사용합니다. 이것이 서버-클라이언트 모델에 적합하기 때문입니다. 모든 요청간에 공유되는 리소스를 관리하려고하지 않는 한 비동기 패턴을 사용하여 더 나은 확장을 시도하는 내부 코드를 내부적으로 작성해야하는 경우에는 이미 래핑되어 있기 때문에 실제로 개선 된 부분은 보이지 않습니다. 다른 것을 차단하지 않는 비동기 프로세스에서.

궁극적으로 시스템에서 병목 현상을 일으키는 원인을 실제로 확인할 때까지는 모두 주관적입니다. 때로는 대기중인 리소스 차단을 방지하여 비동기 패턴이 도움이되는 위치가 분명합니다. 궁극적으로 시스템 측정 및 분석 만이 효율성을 얻을 수있는 곳을 나타낼 수 있습니다.

편집하다:

귀하의 예에서 Task.Factory.StartNew호출은 .NET 스레드 풀에서 작업을 대기시킵니다. 스레드 풀 스레드의 특성은 많은 스레드를 작성 / 파기하는 비용을 피하기 위해 재사용해야합니다. 작업이 완료되면 스레드가 풀로 다시 풀려 다른 요청에 의해 재사용됩니다 (가비지 수집기는 작업에서 일부 개체를 만들지 않은 한 실제로 참여하지 않습니다.이 경우 정상적인 경우에 따라 수집 됨) 범위 지정).

ASP.NET에 관한 한 여기에는 특별한 작업이 없습니다. ASP.NET 요청은 비동기 작업과 관계없이 완료됩니다. 스레드 풀이 포화 상태 인 경우 (즉, 요청을 처리하는 데 사용할 수있는 스레드가없고 풀 설정에서 더 많은 스레드를 작성할 수없는 경우) 유일한 문제는 요청이 시작 대기 중으로 차단되는 경우입니다 풀 스레드를 사용할 수있을 때까지 작업 .


답변

예, 스레드 풀의 스레드를 사용합니다. 실제로 모든 질문 등을 다루는 MSDN의 훌륭한 가이드가 있습니다. 나는 그것이 과거에 꽤 유용하다는 것을 알았습니다. 확인 해봐!

http://msdn.microsoft.com/en-us/library/ee728598.aspx

한편 비동기 코드에 대해 듣는 의견 + 제안은 소금 한 덩어리로 가져와야합니다. 우선, 무언가를 비동기식으로 만드는 것이 반드시 확장 성을 향상시키는 것은 아니며 경우에 따라 응용 프로그램의 확장 성을 악화시킬 수 있습니다. “대량의 트래픽 …”에 대해 게시 한 다른 의견도 특정 상황에서만 정확합니다. 실제로는 작업이 수행하는 작업과 시스템의 다른 부분과 어떻게 상호 작용하는지에 달려 있습니다.

요컨대, 많은 사람들이 비동기에 대해 많은 의견을 가지고 있지만 상황에 맞지 않을 수도 있습니다. 정확한 문제에 중점을두고 기본 성능 테스트를 수행하여 비동기 컨트롤러 등이 실제로 응용 프로그램에서 처리하는 것을 확인 합니다 .


답변

우선 MVC가 아니라 스레드 풀을 유지 관리하는 IIS입니다. 따라서 MVC 또는 ASP.NET 응용 프로그램에 대한 모든 요청은 스레드 풀에서 유지 관리되는 스레드에서 제공됩니다. Asynch 앱을 만들 때만 다른 스레드에서이 작업을 호출하고 스레드를 즉시 해제하여 다른 요청을 처리 할 수 ​​있습니다.

MVC에서 스레드 기아가 발생하는 방식과 MVC를 사용하여 최소화하는 방법을 보여주는 세부 비디오 ( http://www.youtube.com/watch?v=wvg13n5V0V0/ “MVC 비동기 컨트롤러 및 스레드 기아”)와 동일하게 설명했습니다. Asynch controllers. 또한 perfmon을 사용하여 요청 큐를 측정하여 MVC 비동기의 요청 큐가 감소하는 방법과 Synch 작업의 최악의 상태를 확인할 수 있습니다.


답변