Próbuję skorzystać z async/await
funkcji ASP.NET w moim projekcie interfejsu API sieci Web. Nie jestem pewien, czy wpłynie to na wydajność mojej usługi Web API. Poniżej przedstawiam przebieg pracy i przykładowy kod z mojej aplikacji.
Przepływ pracy:
Aplikacja UI → Punkt końcowy Web API (kontroler) → Metoda wywołania w warstwie usług Web API → Wywołaj inną zewnętrzną usługę internetową. (Tutaj mamy interakcje bazy danych itp.)
Kontroler:
public async Task<IHttpActionResult> GetCountries()
{
var allCountrys = await CountryDataService.ReturnAllCountries();
if (allCountrys.Success)
{
return Ok(allCountrys.Domain);
}
return InternalServerError();
}
Warstwa usług:
public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
return Task.FromResult(response);
}
Przetestowałem powyższy kod i działa. Ale nie jestem pewien, czy jest to właściwe użycie async/await
. Podziel się swoimi przemyśleniami.
Odpowiedzi:
Należy pamiętać, że podstawową zaletą kodu asynchronicznego po stronie serwera jest skalowalność . W magiczny sposób nie sprawi, że Twoje żądania będą działać szybciej.
async
W moim artykule na temat ASP.NET omówię kilka kwestii, które należy wziąć pod uwagęasync
.Myślę, że Twój przypadek użycia (wywoływanie innych interfejsów API) jest odpowiedni dla kodu asynchronicznego, pamiętaj tylko, że „asynchroniczny” nie oznacza „szybszy”. Najlepszym podejściem jest ustawienie najpierw responsywnego i asynchronicznego interfejsu użytkownika ; dzięki temu Twoja aplikacja będzie działać szybciej, nawet jeśli jest to nieco wolniej.
Jeśli chodzi o kod, nie jest to asynchroniczne:
Potrzebujesz prawdziwie asynchronicznej implementacji, aby uzyskać korzyści związane ze skalowalnością
async
:Lub (jeśli twoja logika w tej metodzie naprawdę jest tylko przejściem):
Zwróć uwagę, że łatwiej jest pracować „od wewnątrz” niż „od zewnątrz do wewnątrz” w ten sposób. Innymi słowy, nie rozpoczynaj od asynchronicznej akcji kontrolera, a następnie wymuszaj asynchroniczne metody podrzędne. Zamiast tego zidentyfikuj naturalnie asynchroniczne operacje (wywoływanie zewnętrznych interfejsów API, zapytania do bazy danych itp.) I ustaw je jako asynchroniczne najpierw na najniższym poziomie (
Service.ProcessAsync
). Następnie pozwól, abyasync
ściekała, sprawiając, że akcje kontrolera będą asynchroniczne jako ostatni krok.I w żadnym wypadku nie powinieneś używać
Task.Run
w tym scenariuszu.źródło
Jest poprawny, ale być może nieprzydatny.
Ponieważ nie ma na co czekać - nie ma wywołań blokujących interfejsów API, które mogłyby działać asynchronicznie - wtedy konfigurujesz struktury do śledzenia operacji asynchronicznych (które mają narzut), ale nie korzystasz z tej możliwości.
Na przykład, jeśli warstwa usług wykonywała operacje DB z Entity Framework, która obsługuje wywołania asynchroniczne:
Pozwoliłbyś wątkowi robocemu na zrobienie czegoś innego, podczas gdy baza danych była odpytywana (a tym samym mogła przetworzyć inne żądanie).
Oczekiwanie wydaje się być czymś, co musi spaść do końca: bardzo trudno jest dopasować go do istniejącego systemu.
źródło
Nie wykorzystujesz efektywnie async / await, ponieważ wątek żądania zostanie zablokowany podczas wykonywania metody synchronicznej
ReturnAllCountries()
Wątek, który jest przypisany do obsługi żądania, będzie bezczynnie czekał,
ReturnAllCountries()
aż będzie działać.Jeśli możesz zaimplementować
ReturnAllCountries()
w sposób asynchroniczny, zobaczysz korzyści związane ze skalowalnością. Dzieje się tak, ponieważ wątek może zostać zwolniony z powrotem do puli wątków platformy .NET w celu obsługi innego żądania podczasReturnAllCountries()
wykonywania. Pozwoliłoby to Twojej usłudze mieć wyższą przepustowość dzięki wydajniejszemu wykorzystaniu wątków.źródło
Zmieniłbym Twoją warstwę usług na:
tak jak masz, nadal prowadzisz
_service.Process
połączenie synchronicznie, a oczekiwanie na nie przynosi znikome korzyści lub wcale.Dzięki takiemu podejściu zawijasz potencjalnie wolne połączenie w a
Task
, uruchamiasz je i zwracasz na oczekiwanie. Teraz możesz oczekiwać naTask
.źródło
Service
nie jest metodą asynchroniczną i nie jest oczekiwana w samymService
wywołaniu. Rozumiem jego punkt widzenia, ale nie wierzę, że ma to zastosowanie tutaj.Task.Run
należy unikać w ASP.NET. Takie podejście usuwałoby wszystkie korzyści zasync
/await
i faktycznie działało gorzej pod obciążeniem niż tylko wywołanie synchroniczne.