Použití asynchronních metod v ASP.NET MVC 4

Rick Anderson

V tomto kurzu se naučíte základy vytváření asynchronní webové aplikace ASP.NET MVC pomocí Visual Studio Express 2012 for Web, což je bezplatná verze sady Microsoft Visual Studio. Můžete také použít Visual Studio 2012.

Kompletní ukázka je k dispozici pro tento kurz na GitHubu. https://github.com/RickAndMSFT/Async-ASP.NET/

Třída kontroleru ASP.NET MVC 4 v kombinaci .NET 4.5 umožňuje psát asynchronní metody akcí, které vrací objekt typu Task<ActionResult>. Rozhraní .NET Framework 4 zavedlo asynchronní programovací koncept označovaný jako úloha a ASP.NET MVC 4 podporuje úlohu. Úkoly jsou reprezentovány typem úlohy a souvisejícími typy v oboru názvů System.Threading.Tasks . Rozhraní .NET Framework 4.5 staví na této asynchronní podpoře s klíčovými slovy await a async , díky kterým je práce s objekty Task mnohem méně složitá než předchozí asynchronní přístupy. Klíčové slovo await je syntaktické zkratky pro označení, že část kódu by měla asynchronně čekat na nějakou jinou část kódu. Klíčové slovo async představuje nápovědu, kterou můžete použít k označení metod jako asynchronních metod založených na úlohách. Kombinace funkcí await, async a Task objektu usnadňuje psaní asynchronního kódu v .NET 4.5. Nový model pro asynchronní metody se nazývá Asynchronní vzor založený na úlohách (TAP). V tomto kurzu se předpokládá, že máte určitou znalost asynchronního programování pomocí klíčových slov await a async a oboru názvů úloh .

Další informace o použití klíčových slov await a async a oboru názvů úloh najdete v následujících odkazech.

Způsob zpracování požadavků fondem vláken

Na webovém serveru udržuje rozhraní .NET Framework fond vláken, které se používají ke službě ASP.NET požadavků. Po přijetí požadavku se odešle vlákno z fondu, které tento požadavek zpracuje. Pokud je požadavek zpracován synchronně, vlákno, které zpracovává požadavek, je zaneprázdněno během zpracování požadavku a toto vlákno nemůže zpracovat jiný požadavek.

To nemusí být problém, protože fond vláken může být dostatečně velký, aby pojal mnoho zaneprázdněných vláken. Počet vláken ve fondu vláken je však omezený (výchozí maximum pro .NET 4.5 je 5 000). Ve velkých aplikacích s vysokou souběžností dlouhotrvajících požadavků můžou být všechna dostupná vlákna zaneprázdněná. Tato podmínka se označuje jako vyhladovění vláken. Po dosažení této podmínky webový server zasadí požadavky do fronty. Pokud se fronta požadavků zaplní, webový server odmítne požadavky se stavem HTTP 503 (Server je příliš zaneprázdněn). Fond vláken CLR má omezení týkající se injektáže nových vláken. Pokud je souběžnost nárazová (to znamená, že váš web může náhle získat velký počet požadavků) a všechna dostupná vlákna požadavků jsou zaneprázdněná kvůli volání back-endu s vysokou latencí, může omezená rychlost injektáže vláken způsobovat, že vaše aplikace reaguje velmi špatně. Kromě toho každé nové vlákno přidané do fondu vláken má režii (například 1 MB paměti zásobníku). Webová aplikace používající synchronní metody pro obsluhu volání s vysokou latencí, kdy fond vláken roste na výchozí maximum .NET 4.5 na 5, 000 vláken, by spotřebovala přibližně o 5 GB více paměti než aplikace schopná služby stejné požadavky pomocí asynchronních metod a pouze 50 vláken. Když provádíte asynchronní práci, nepoužíváte vždy vlákno. Když například vytvoříte asynchronní požadavek webové služby, ASP.NET nebude používat žádná vlákna mezi voláním asynchronní metody a await. Použití fondu vláken ke zpracování požadavků s vysokou latencí může vést k velkému využití paměti a špatnému využití hardwaru serveru.

Zpracování asynchronních požadavků

Ve webové aplikaci, která při spuštění vidí velký počet souběžných požadavků nebo má nárazové zatížení (při náhlém zvýšení souběžnosti), zvýší asynchronní volání webové služby rychlost odezvy aplikace. Zpracování asynchronního požadavku trvá stejně dlouho jako synchronní požadavek. Pokud požadavek provede volání webové služby, které vyžaduje dvě sekundy, trvá žádost dvě sekundy, ať už se provádí synchronně nebo asynchronně. Během asynchronního volání ale vlákno neblokuje, aby reagovalo na jiné požadavky, zatímco čeká na dokončení prvního požadavku. Proto asynchronní požadavky brání řazení požadavků do fronty a nárůstu fondu vláken, pokud existuje mnoho souběžných požadavků, které vyvolávají dlouhotrvající operace.

Výběr synchronních nebo asynchronních metod akcí

Tato část obsahuje pokyny pro použití synchronních nebo asynchronních metod akcí. To jsou jenom pokyny; prozkoumejte každou aplikaci zvlášť a zjistěte, jestli asynchronní metody pomáhají s výkonem.

Obecně platí, že synchronní metody používejte pro následující podmínky:

  • Operace jsou jednoduché nebo krátkodobé.
  • Jednoduchost je důležitější než efektivita.
  • Operace jsou primárně operace procesoru místo operací, které zahrnují velkou režii disku nebo sítě. Použití asynchronních metod akcí u operací vázaných na procesor neposkytuje žádné výhody a vede k vyšší režii.

Obecně platí, že asynchronní metody používejte pro následující podmínky:

  • Voláte služby, které je možné využívat asynchronními metodami, a používáte .NET 4.5 nebo novější.
  • Operace jsou vázané na síť nebo vstupně-výstupní operace, nikoli na procesor.
  • Paralelismus je důležitější než jednoduchost kódu.
  • Chcete poskytnout mechanismus, který uživatelům umožní zrušit dlouhotrvající požadavek.
  • Když výhoda přepínání vláken převáží náklady na kontextový přepínač. Obecně byste měli metodu nastavit jako asynchronní, pokud synchronní metoda čeká na vlákně požadavku ASP.NET a přitom nepracuje. Asynchronním voláním se vlákno požadavku ASP.NET nezablokuje a nepracuje, zatímco čeká na dokončení požadavku webové služby.
  • Testování ukazuje, že blokující operace jsou kritickým bodem výkonu lokality a že služba IIS může obsluhovat více požadavků pomocí asynchronních metod pro tato blokující volání.

Ukázka ke stažení ukazuje, jak efektivně používat asynchronní metody akcí. Poskytnutá ukázka byla navržena tak, aby poskytovala jednoduchou ukázku asynchronního programování v ASP.NET MVC 4 pomocí .NET 4.5. Ukázka není určená jako referenční architektura pro asynchronní programování v ASP.NET MVC. Ukázkový program volá ASP.NET metody webového rozhraní API , které následně volají Task.Delay pro simulaci dlouhotrvajících volání webové služby. Většina produkčních aplikací nebude vykazovat tak zřejmé výhody použití asynchronních metod akcí.

Několik aplikací vyžaduje, aby všechny metody akcí byly asynchronní. Převod několika synchronních metod akcí na asynchronní metody často poskytuje nejlepší zvýšení efektivity potřebného množství práce.

Ukázková aplikace

Ukázkovou aplikaci si můžete stáhnout z https://github.com/RickAndMSFT/Async-ASP.NET/ webu GitHubu . Úložiště se skládá ze tří projektů:

  • Mvc4Async: Projekt ASP.NET MVC 4, který obsahuje kód použitý v tomto kurzu. Provádí volání webového rozhraní API do služby WebAPIpgw .
  • WebAPIpgw: Projekt webového rozhraní API ASP.NET MVC 4, který implementuje Products, Gizmos and Widgets kontrolery. Poskytuje data pro projekt WebAppAsync a projekt Mvc4Async .
  • WebAppAsync: Projekt ASP.NET Web Forms použitý v jiném kurzu.

Metoda synchronní akce Gizmos

Následující kód ukazuje synchronní metodu Gizmos akce, která se používá k zobrazení seznamu gizmos. (V tomto článku je gizmo fiktivní mechanické zařízení.)

public ActionResult Gizmos()
{
    ViewBag.SyncOrAsync = "Synchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", gizmoService.GetGizmos());
}

Následující kód ukazuje GetGizmos metodu služby gizmo.

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)
            );
        }
    }
}

Metoda GizmoService GetGizmos předá identifikátor URI službě HTTP ASP.NET webového rozhraní API, která vrací seznam dat gizmos. Projekt WebAPIpgw obsahuje implementaci webového rozhraní API gizmos, widget a product kontrolerů.
Následující obrázek znázorňuje zobrazení gizmos z ukázkového projektu.

Gizmos

Vytvoření asynchronní metody akce Gizmos

Ukázka používá nová klíčová slova async a await (k dispozici v .NET 4.5 a sadě Visual Studio 2012), aby kompilátor zodpovídá za údržbu složitých transformací potřebných pro asynchronní programování. Kompilátor umožňuje psát kód pomocí konstruktorů synchronního toku řízení jazyka C# a kompilátor automaticky použije transformace potřebné k použití zpětných volání, aby se zabránilo blokování vláken.

Následující kód ukazuje synchronní Gizmos metodu a asynchronní metodu GizmosAsync . Pokud váš prohlížeč podporuje element HTML 5<mark>, uvidíte změny ve GizmosAsync žlutém zvýraznění.

public ActionResult Gizmos()
{
    ViewBag.SyncOrAsync = "Synchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", gizmoService.GetGizmos());
}
public async Task<ActionResult> GizmosAsync()
{
    ViewBag.SyncOrAsync = "Asynchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos", await gizmoService.GetGizmosAsync());
}

Následující změny byly použity, aby byl GizmosAsync objekt asynchronní.

  • Metoda je označena klíčovým slovem async , které kompilátoru říká, aby vygeneroval zpětná volání pro části těla a automaticky vytvořil vrácenou metodu Task<ActionResult> .
  • K názvu metody bylo připojeno "Async". Připojení "Async" se nevyžaduje, ale je to konvence při psaní asynchronních metod.
  • Návratový typ se změnil z ActionResult na Task<ActionResult>. Návratový typ Task<ActionResult> představuje probíhající práci a poskytuje volajícím metody popisovač, kterým mají čekat na dokončení asynchronní operace. V tomto případě je volající webová služba. Task<ActionResult> představuje probíhající práci s výsledkem ActionResult.
  • Klíčové slovo await se použilo pro volání webové služby.
  • Asynchronní rozhraní API webové služby bylo volána (GetGizmosAsync).

GetGizmosAsync Uvnitř těla metody je volána další asynchronní metodaGetGizmosAsync. GetGizmosAsync okamžitě vrátí hodnotu Task<List<Gizmo>> , která se nakonec dokončí, jakmile budou data k dispozici. Protože nechcete dělat nic jiného, dokud nebudete mít data gizmo, kód čeká na úkol (pomocí klíčového slova await ). Klíčové slovo await můžete použít pouze v metodách opatřených poznámkami s klíčovým slovem async .

Klíčové slovo await neblokuje vlákno, dokud se úloha nedokončí. Zaregistruje zbytek metody jako zpětné volání úkolu a okamžitě se vrátí. Jakmile se očekávaná úloha nakonec dokončí, vyvolá toto zpětné volání a obnoví tak provádění metody přímo tam, kde skončila. Další informace o používání klíčových slov await a async a oboru názvů úloh najdete v referenčních odkazech na async.

Následující kód ukazuje metody GetGizmos a GetGizmosAsync.

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 (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri);
        return (await response.Content.ReadAsAsync<List<Gizmo>>());
    }
}

Asynchronní změny jsou podobné těm, které byly provedeny v GizmosAsync výše.

Následující obrázek znázorňuje asynchronní zobrazení gizmo.

Asynchronní

Prezentace dat gizmos v prohlížeči je shodná s zobrazením vytvořeným synchronním voláním. Jediným rozdílem je, že asynchronní verze může být výkonnější při velkém zatížení.

Paralelní provádění více operací

Asynchronní metody akcí mají oproti synchronním metodám významnou výhodu, když akce musí provést několik nezávislých operací. V poskytnuté ukázce synchronní metoda PWG(pro Produkty, Widgety a Gizmos) zobrazí výsledky tří volání webové služby, aby získala seznam produktů, widgetů a gizmos. Projekt webového rozhraní API ASP.NET , který poskytuje tyto služby, používá Task.Delay k simulaci latence nebo pomalých síťových volání. Pokud je zpoždění nastaveno na 500 milisekund, asynchronní PWGasync metoda trvá něco málo přes 500 milisekund, zatímco synchronní PWG verze trvá více než 1500 milisekund. Synchronní PWG metoda je znázorněna v následujícím kódu.

public ActionResult PWG()
{
    ViewBag.SyncType = "Synchronous";
    var widgetService = new WidgetService();
    var prodService = new ProductService();
    var gizmoService = new GizmoService();

    var pwgVM = new ProdGizWidgetVM(
        widgetService.GetWidgets(),
        prodService.GetProducts(),
        gizmoService.GetGizmos()
       );

    return View("PWG", pwgVM);
}

Asynchronní PWGasync metoda je zobrazena v následujícím kódu.

public async Task<ActionResult> PWGasync()
{
    ViewBag.SyncType = "Asynchronous";
    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
       );

    return View("PWG", pwgVM);
}

Následující obrázek znázorňuje zobrazení vrácené metodou PWGasync .

pwgAsync

Použití tokenu zrušení

Asynchronní metody akcí vracejí Task<ActionResult>se zrušit, to znamená, že přebírají parametr CancellationToken , pokud je k dispozici s atributem AsyncTimeout . Následující kód ukazuje metodu GizmosCancelAsync s časovým limitem 150 milisekund.

[AsyncTimeout(150)]
[HandleError(ExceptionType = typeof(TimeoutException),
                                    View = "TimeoutError")]
public async Task<ActionResult> GizmosCancelAsync(
                       CancellationToken cancellationToken )
{
    ViewBag.SyncOrAsync = "Asynchronous";
    var gizmoService = new GizmoService();
    return View("Gizmos",
        await gizmoService.GetGizmosAsync(cancellationToken));
}

Následující kód ukazuje přetížení GetGizmosAsync, které přebírá parametr CancellationToken .

public async Task<List<Gizmo>> GetGizmosAsync(string uri,
    CancellationToken cancelToken = default(CancellationToken))
{
    using (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri, cancelToken);
        return (await response.Content.ReadAsAsync<List<Gizmo>>());
    }
}

V zadané ukázkové aplikaci výběr odkazu Demo tokenu zrušení zavolá metodu GizmosCancelAsync a demonstruje zrušení asynchronního volání.

Konfigurace serveru pro volání webové služby s vysokou souběžností nebo vysokou latencí

Abyste mohli využít výhody asynchronní webové aplikace, možná budete muset provést určité změny ve výchozí konfiguraci serveru. Při konfiguraci a zátěžovém testování asynchronní webové aplikace mějte na paměti následující skutečnosti.