ASP.NET Web API에서 효과적으로 async / await 사용


114

async/await내 웹 API 프로젝트에서 ASP.NET 의 기능을 사용하려고합니다 . 내 웹 API 서비스의 성능에 어떤 차이가 있는지 잘 모르겠습니다. 내 응용 프로그램의 워크 플로 및 샘플 코드를 아래에서 찾으십시오.

작업 흐름 :

UI Application → Web API endpoint (controller) → Web API 서비스 레이어에서 메서드 호출 → 다른 외부 웹 서비스 호출. (여기에 DB 상호 작용 등이 있습니다.)

제어 장치:

public async Task<IHttpActionResult> GetCountries()
{
    var allCountrys = await CountryDataService.ReturnAllCountries();

    if (allCountrys.Success)
    {
        return Ok(allCountrys.Domain);
    }

    return InternalServerError();
}

서비스 계층 :

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");

    return Task.FromResult(response);
}

위의 코드를 테스트하고 작동 중입니다. 그러나 그것이 올바른 사용법인지 확실하지 않습니다 async/await. 여러분의 생각을 공유 해주세요.


답변:


202

API의 성능에 어떤 차이가 있는지 잘 모르겠습니다.

서버 측에서 비동기 코드의 주요 이점은 확장 성 입니다. 마술처럼 요청이 더 빠르게 실행되지는 않습니다. ASP.NET 에 대한async기사에서async 몇 가지 "사용해야 하는가 "고려 사항을 다룹니다 .

사용 사례 (다른 API 호출)가 비동기 코드에 적합하다고 생각합니다. "비동기"가 "더 빠름"을 의미하지는 않는다는 점을 명심하십시오. 가장 좋은 방법은 먼저 UI를 반응 형 및 비동기식으로 만드는 것입니다 . 이렇게하면 앱 약간 느려도 더 빠르게 느껴 집니다.

코드가 진행되는 한 이것은 비동기가 아닙니다.

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
  var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
  return Task.FromResult(response);
}

다음과 같은 확장 성 이점을 얻으려면 진정한 비동기 구현이 필요합니다 async.

public async Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync()
{
  return await _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
}

또는 (이 메서드의 논리가 실제로 통과 일 경우) :

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync()
{
  return _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
}

이와 같이 "외부"보다 "내부"에서 작업하는 것이 더 쉽습니다. 즉, 비동기 컨트롤러 작업으로 시작하지 말고 다운 스트림 메서드를 비동기로 강제 설정하십시오. 대신 자연스러운 비동기 작업 (외부 API 호출, 데이터베이스 쿼리 등)을 식별하고 가장 낮은 수준에서 먼저 비동기 작업을 만듭니다 ( Service.ProcessAsync). 그런 다음 async마지막 단계로 컨트롤러 작업을 비동기식으로 만들어서 조금씩 흐르게하십시오.

Task.Run이 시나리오 에서는 어떤 상황에서도 사용해서는 안됩니다 .


4
소중한 의견을 보내 주신 Stephen에게 감사드립니다. 모든 서비스 계층 메서드를 비동기로 변경했으며 이제 ExecuteTaskAsync 메서드를 사용하여 외부 REST 호출을 호출하고 예상대로 작동합니다. 또한 비동기 및 작업에 관한 블로그 게시물에 감사드립니다. 초기 이해에 많은 도움이되었습니다.
arp

5
여기서 Task.Run을 사용하지 말아야하는 이유를 설명해 주시겠습니까?
Maarten

1
@Maarten : 링크 한 기사 에서 간략하게 설명합니다 . 내 블로그에서 더 자세히 설명 합니다 .
Stephen Cleary

나는 당신의 기사 Stephen을 읽었고 무언가 언급하는 것을 피하는 것 같습니다. ASP.NET 요청이 도착하여 스레드 풀 스레드에서 실행되기 시작하면 괜찮습니다. 그러나 비동기 화되면 예는 비동기 처리를 시작하고 해당 스레드는 즉시 풀로 돌아갑니다. 그러나 비동기 메서드에서 수행되는 작업은 스레드 풀 스레드에서 자체적으로 실행됩니다!
Hugh


12

정확하지만 유용하지 않을 수 있습니다.

기다릴 것이 없으므로 (비동기 적으로 작동 할 수있는 차단 API에 대한 호출이 없음) 비동기 작업 (오버 헤드가 있음)을 추적하는 구조를 설정 한 다음 해당 기능을 사용하지 않습니다.

예를 들어 서비스 계층이 비동기 호출을 지원하는 Entity Framework로 DB 작업을 수행하는 경우 :

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    using (db = myDBContext.Get()) {
      var list = await db.Countries.Where(condition).ToListAsync();

       return list;
    }
}

db가 쿼리되는 동안 작업자 스레드가 다른 작업을 수행하도록 허용하여 다른 요청을 처리 할 수 ​​있습니다.

Await는 완전히 내려 가야하는 경향이 있습니다. 기존 시스템에 개조하는 것은 매우 어렵습니다.


-1

동기 메서드 를 실행하는 동안 요청 스레드가 차단되므로 비동기 / 대기 효과를 효과적으로 활용하지 못합니다.ReturnAllCountries()

요청을 처리하기 위해 할당 된 스레드는 ReturnAllCountries()작동 하는 동안 유휴 상태로 대기 합니다.

ReturnAllCountries()비동기식으로 구현할 수 있다면 확장 성 이점을 얻을 수 있습니다. 스레드 ReturnAllCountries()가 실행 되는 동안 다른 요청을 처리하기 위해 .NET 스레드 풀로 다시 해제 될 수 있기 때문 입니다. 이렇게하면 스레드를보다 효율적으로 활용하여 서비스의 처리량을 높일 수 있습니다.


이것은 사실이 아닙니다. 소켓이 연결되어 있지만 요청을 처리하는 스레드는 서비스 호출을 기다리는 동안 다른 요청을 처리하는 등 다른 작업을 수행 할 수 있습니다.
Garr Godfrey

-8

서비스 계층을 다음과 같이 변경합니다.

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    return Task.Run(() =>
    {
        return _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
    }      
}

당신이 그것을 가지고 있기 때문에, 당신은 여전히 ​​당신의 _service.Process호출을 동 기적으로 실행 하고 있으며 그것을 기다리는 것으로부터 거의 또는 전혀 이익을 얻지 못합니다.

이 접근 방식을 사용하면 잠재적으로 느린 호출을에서 래핑하고 Task시작하고 대기 하도록 반환합니다. 이제 Task.


3
블로그 링크에 따르면 Task.Run을 사용하더라도 메서드가 여전히 동 기적으로 실행된다는 것을 알 수 있습니다.
arp

흠. 위의 코드와 해당 링크에는 많은 차이가 있습니다. 귀하 Service는 비동기 메서드가 아니며 Service호출 자체 내에서 대기 하지 않습니다 . 나는 그의 요점을 이해할 수 있지만 여기에 적용되지 않는다고 생각합니다.
Jonesopolis

8
Task.RunASP.NET에서는 피해야합니다. 이 접근 방식은 async/ 에서 모든 이점을 제거 await하고 실제로는 동기 호출보다 부하가 더 적습니다.
Stephen Cleary
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.