동기화 작업 대신 비동기 WebAPI 작업을 만들어야하는 이유는 무엇입니까?


109

내가 만든 Web API에 다음 작업이 있습니다.

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public CartTotalsDTO GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh);
}

이 웹 서비스에 대한 호출은 다음과 같이 Jquery Ajax 호출을 통해 수행됩니다.

$.ajax({
      url: "/api/products/pharmacies/<%# Farmacia.PrimaryKeyId.Value.ToString() %>/page/" + vm.currentPage() + "/" + filter,
      type: "GET",
      dataType: "json",
      success: function (result) {
          vm.items([]);
          var data = result.Products;
          vm.totalUnits(result.TotalUnits);
      }          
  });

이 방법으로 이전 작업을 구현하는 일부 개발자를 보았습니다.

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return await Task.Factory.StartNew(() => delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh));
}

그래도 GetProductsWithHistory ()는 꽤 긴 작업입니다. 내 문제와 컨텍스트를 감안할 때 webAPI 작업을 비동기식으로 만들면 어떤 이점이 있습니까?


1
클라이언트 측은 이미 비동기 인 AJAX를 사용합니다. 서비스를 async Task<T>. TPL도 :) 존재하기 전에 AJAX 구현 된 기억
도미닉 Zukiewicz

65
비동기 컨트롤러를 구현하는 이유를 이해해야하지만 대부분은 그렇지 않습니다. IIS에는 사용 가능한 스레드 수가 제한되어 있으며 모두 사용 중일 때 서버는 새 요청을 처리 할 수 ​​없습니다. 프로세스가 I / O가 완료 될 때까지 대기 할 때 비동기 컨트롤러를 사용하면 해당 스레드가 서버가 다른 요청을 처리하는 데 사용할 수 있도록 해제됩니다.
Matija Grcic 2014.10.02

3
어떤 개발자가 그렇게하는 것을 보셨습니까? 그 기술을 추천하는 블로그 게시물이나 기사가 있으면 링크를 게시하십시오.
Stephen Cleary 2014 년

3
프로세스가 최상위 (웹 애플리케이션 자체 및 컨트롤러 포함)에서 프로세스 외부로 나가는 대기 가능한 활동 (타이머 지연, 파일 I / O, DB 액세스, 및 웹 요청). 이 경우, 대리인 도우미는 필요 GetProductsWithHistoryAsync()반환 Task<CartTotalsDTO>. 비동기로 만드는 호출을 마이그레이션하려는 경우 컨트롤러 비동기를 작성하면 이점이있을 수 있습니다. 그런 다음 나머지 부분을 마이그레이션하면서 비동기 부분의 이점을 얻기 시작합니다.
Keith Robertson

1
수행중인 프로세스가 시작되어 데이터베이스에 도달하면 웹 스레드는 해당 스레드가 다시 돌아올 때까지 기다리고 있습니다. 최대 스레드 수에 도달하고 다른 요청이 들어 오면 기다려야합니다. 왜 그럴까요? 대신 컨트롤러에서 해당 스레드를 해제하여 다른 요청이이를 사용하고 데이터베이스의 원래 요청이 돌아 왔을 때만 다른 웹 스레드를 차지할 수 있습니다. msdn.microsoft.com/en-us/magazine/dn802603.aspx
user441521

답변:


98

특정 예에서 작업은 비동기식이 아니므로 수행하는 작업은 비동기식입니다. 한 스레드를 해제하고 다른 스레드를 차단하는 것입니다. 모든 스레드가 스레드 풀 스레드이기 때문에 그럴 이유가 없습니다 (GUI 응용 프로그램과 달리).

"동기화를 통한 비 동기화"에 대한 논의에서 내부적으로 동기식으로 구현되는 API가있는 경우 단순히 동기식 메서드를 Task.Run.

에서 해야 내가 비동기 메서드에 대한 동기 래퍼를 노출?

그러나 async결과를 기다리는 스레드를 차단하는 대신 실제 비동기 작업 (일반적으로 I / O)이있는 WebAPI 호출을 수행하면 스레드가 스레드 풀로 돌아가서 다른 작업을 수행 할 수 있습니다. 즉, 애플리케이션이 더 적은 리소스로 더 많은 작업을 수행 할 수 있으며 확장 성이 향상됩니다.


3
@efaruk 모든 스레드는 작업자 스레드입니다. 하나의 ThreadPool 스레드를 해제하고 다른 스레드를 차단하는 것은 의미가 없습니다.
i3arnon

1
@efaruk 당신이 무슨 말을 하려는지 잘 모르겠습니다.하지만 WebAPI에서 비동기를 동기화 할 이유가 없다는 데 동의하는 한 괜찮습니다.
i3arnon

@efaruk "async over sync"(즉 await Task.Run(() => CPUIntensive()))는 asp.net에서 쓸모가 없습니다. 당신은 그렇게해서 아무것도 얻지 못합니다. 하나의 ThreadPool 스레드를 해제하여 다른 스레드를 차지합니다. 단순히 동기식 메서드를 호출하는 것보다 덜 효율적입니다.
i3arnon

1
@efaruk 아니요, 합리적이지 않습니다. 예제는 독립적 인 작업을 순차적으로 실행합니다. 추천하기 전에 asyc / await를 읽어야합니다. await Task.WhenAll병렬로 실행 하려면을 사용해야 합니다.
Søren Boisen

1
@efaruk Boisen이 설명했듯이 귀하의 예제는 단순히 이러한 동기식 메서드를 순차적으로 호출하는 것 외에 값을 추가하지 않습니다. Task.Run여러 스레드에서로드를 병렬화하려는 경우 사용할 수 있지만 이것이 "동기화를 통한 비 동기화"가 의미하는 것은 아닙니다. "동기화에 대한 비동기"는 동기식에 대한 래퍼로 비동기 메서드를 만드는 것을 참조합니다. 내 대답의 인용문에서 볼 수 있습니다.
i3arnon

1

한 가지 접근 방식은 (고객 응용 프로그램에서 성공적으로 사용했습니다) Windows 서비스가 작업자 스레드로 긴 작업을 실행하도록 한 다음 IIS에서이를 수행하여 차단 작업이 완료 될 때까지 스레드를 해제하는 것입니다. 참고, 이것은 결과는 테이블 (jobId로 식별 된 행)에 저장되고 사용 후 몇 시간 후에 정리되는 클리너 프로세스가 있습니다.

"내 문제와 컨텍스트를 고려할 때 webAPI 작업을 비동기식으로 만들면 어떤 이점이 있습니까?"라는 질문에 답할 수 있습니다. "매우 긴 작업"이라는 점을 감안할 때 ms가 아닌 몇 초 동안 생각하고 있으므로이 접근 방식은 IIS 스레드를 해제합니다. 당연히 리소스를 사용하는 Windows 서비스를 실행해야하지만이 접근 방식은 느린 쿼리가 시스템의 다른 부분에서 스레드를 훔치는 것을 방지 할 수 있습니다.

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
        var jobID = Guid.NewGuid().ToString()
        var job = new Job
        {
            Id = jobId,
            jobType = "GetProductsWithHistory",
            pharmacyId = pharmacyId,
            page = page,
            filter = filter,
            Created = DateTime.UtcNow,
            Started = null,
            Finished = null,
            User =  {{extract user id in the normal way}}
        };
        jobService.CreateJob(job);

        var timeout = 10*60*1000; //10 minutes
        Stopwatch sw = new Stopwatch();
        sw.Start();
        bool responseReceived = false;
        do
        {
            //wait for the windows service to process the job and build the results in the results table
            if (jobService.GetJob(jobId).Finished == null)
            {
                if (sw.ElapsedMilliseconds > timeout ) throw new TimeoutException();
                await Task.Delay(2000);
            }
            else
            {
                responseReceived = true;
            }
        } while (responseReceived == false);

    //this fetches the results from the temporary results table
    return jobService.GetProductsWithHistory(jobId);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.