ASP.NET Core Osvědčené postupy z oblasti výkonu

Mike Rousos

Tento článek obsahuje pokyny k osvědčeným postupům z oblasti výkonu ASP.NET Core.

Agresivní ukládání do mezipaměti

Ukládání do mezipaměti je probíráno v několika částech tohoto dokumentu. Další informace naleznete v tématu Ukládání odpovědí do mezipaměti v ASP.NET Core.

Principy aktivních cest kódu

V tomto dokumentu je cesta horkého kódu definována jako cesta kódu, která se často volá a kde dochází k velké části doby provádění. Cesty s horkým kódem obvykle omezují horizontální navýšení velikosti a výkon aplikace a jsou popsány v několika částech tohoto dokumentu.

Vyhněte se blokujícím voláním

ASP.NET Core aplikace by měly být navržené tak, aby zpracovávaly mnoho požadavků současně. Asynchronní rozhraní API umožňují malému fondu vláken zpracovávat tisíce souběžných požadavků tím, že nečeká na blokující volání. Místo čekání na dokončení dlouhotrací synchronní úlohy může vlákno fungovat na jiném požadavku.

Běžným problémem s výkonem ASP.NET Core aplikacích je blokování volání, která mohou být asynchronní. Mnoho synchronních blokujících volání vede k vyhřešování fondu vláken a degradaci doby odezvy.

Nepoužívejte:

  • Asynchronní provádění můžete blokovat voláním metody Task.Wait nebo Task.Result.
  • Získání zámků v běžných cestách kódu ASP.NET Core aplikace jsou nejúkonnější, když jsou na jejich architektuě, aby spouštěli kód paralelně.
  • Volejte Task.Run a hned na něj čekejte. ASP.NET Core už spouští kód aplikace na běžných vláknech fondu vláken, takže volání metody Task.Run má za výsledek pouze nadbytečné plánování fondu vláken. I v případě, že by naplánovaný kód blokovali vlákno, Task.Run tomu nezabrání.

Proveďte:

  • Změňte cesty horkého kódu na asynchronní.
  • Pokud je k dispozici asynchronní rozhraní API, volejte asynchronně přístup k datům, vstupně-výstupní rozhraní API a dlouhotrvatá rozhraní API operací. Nepoužívejte Task.Run k tomu, aby synchronní rozhraní API bylo asynchronní.
  • Proveďte asynchronní akce Razor kontroleru nebo stránky. Celý zásobník volání je asynchronní, aby bylo možné využívat vzory async/await.

Profiler, například PerfView, lze použít k vyhledání vláken často přidaných do fondu vláken. Událost Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start označuje vlákno přidané do fondu vláken.

Vrácení velkých kolekcí na několika menších stránkách

Webová stránka by neměla načítat velké objemy dat najednou. Při vracení kolekce objektů zvažte, jestli by mohla vést k problémům s výkonem. Určete, jestli by návrh mohl mít následující špatné výsledky:

Pokud chcete zmírnit výše uvedené scénáře, přidejte stránkování. Pomocí parametrů velikosti stránky a indexu stránky by vývojáři měli upřednostňovat návrh vrácení částečného výsledku. Pokud se vyžaduje vyčerpávající výsledek, mělo by se stránkování použít k asynchronnímu naplnění dávek výsledků, aby se zabránilo zamykání prostředků serveru.

Další informace o stránkování a omezení počtu vrácených záznamů najdete v těchto tématu:

Return IEnumerable<T> nebo IAsyncEnumerable<T>

Vrácení z IEnumerable<T> akce vede k synchronní iteraci kolekce serializátorem. Výsledkem je blokování volání a potenciál pro vyhřešování fondu vláken. Pokud se chcete vyhnout synchronnímu ToListAsync výčtu, použijte před vrácením výčtu.

Počínaje ASP.NET Core 3.0 lze použít jako alternativu k IAsyncEnumerable<T> IEnumerable<T> výčtu asynchronně. Další informace najdete v tématu Návratové typy akce kontroleru.

Minimalizace přidělení velkých objektů

Systém uvolňování paměti .NET Core automaticky spravuje přidělování a uvolňování paměti v ASP.NET Core aplikacích. Automatické uvolňování paměti obecně znamená, že vývojáři se nemusí starat o to, jak nebo kdy se paměť uchová. Vyčištění neodkazovaných objektů ale zabere čas procesoru, takže vývojáři by měli minimalizovat přidělování objektů v horkých cestách kódu. Uvolňování paměti je zvlášť nákladné u velkých objektů (> 85 kB). Velké objekty jsou uloženy na haldě velkých objektů a k vyčištění vyžadují úplné uvolnění paměti (generace 2). Na rozdíl od kolekcí generace 0 a generace 1 vyžaduje kolekce generace 2 dočasné pozastavení provádění aplikace. Časté přidělování a delokace velkých objektů může způsobit nekonzistentní výkon.

Recommendations:

  • Zvažte ukládání velkých objektů do mezipaměti, které se často používají. Ukládání do mezipaměti velké objekty brání nákladné přidělení.
  • Ukládání velkých polí do vyrovnávacích pamětí fondu pomocí <T> Fondu polí.
  • Nepřidělovat mnoho krátkodobých velkých objektů na horké cesty kódu.

Problémy s pamětí, jako je například předchozí, je možné diagnostikovat pomocí statistik uvolňování paměti v nástroji PerfView a prozkoumáním:

  • Doba pozastavení uvolňování paměti.
  • Jaké procento času procesoru je stráveno v uvolňování paměti.
  • Kolik uvolňování paměti je generace 0, 1 a 2.

Další informace najdete v tématu Uvolňování paměti a výkon.

Optimalizace přístupu k datům a V/V

Interakce s úložištěm dat a dalšími vzdálenými službami jsou často nejpomalejšími částmi ASP.NET Core aplikace. Efektivní čtení a zápis dat je důležité pro dobrý výkon.

Recommendations:

  • Všechna rozhraní API pro přístup k datům volejte asynchronně.
  • Nenačítá více dat, než je nezbytné. Napíšete dotazy, které vrátí pouze data potřebná pro aktuální požadavek HTTP.
  • Zvažte ukládání často používaných dat načtených z databáze nebo vzdálené služby do mezipaměti, pokud jsou mírně nená data přijatelná. V závislosti na scénáři použijte MemoryCache nebo DistributedCache. Další informace naleznete v tématu Ukládání odpovědí do mezipaměti v ASP.NET Core.
  • Minimalizujte dobu přenosu v síti. Cílem je načíst požadovaná data v jednom volání, nikoli v několika voláních.
  • Při přístupu k datům pro účely jen pro čtení Entity Framework Core dotazy bez sledování v nástroji . EF Core vracet výsledky dotazů bez sledování efektivněji.
  • Filtrujte a agregujte dotazy LINQ (například pomocí příkazů , nebo ), aby filtrování .Where .Select .Sum prováděla databáze.
  • Zvažte, že EF Core některých operátorů dotazů na klientovi, což může vést k neefektivnímu provádění dotazů. Další informace najdete v tématu Problémy s výkonem vyhodnocení klienta.
  • Nepoužívejte projekční dotazy na kolekce, což může vést ke spuštění N + 1 SQL dotazů. Další informace najdete v tématu Optimalizace korelovaných poddotazů.

Přístupy, které mohou zlepšit výkon aplikací ve velkém měřítku, najdete v tématu Vysoký výkon EF:

Před potvrzením základu kódu doporučujeme měřit dopad předchozích vysoce výkonných přístupů. Další složitost zkompilovaných dotazů nemusí odůvodnit zlepšení výkonu.

Problémy s dotazy můžete zjistit tak, že si prohlédněte čas strávený přístupem k datům pomocí nástrojů Přehledy nebo nástrojů pro profilaci. Většina databází také z zveřejňuje statistiky týkající se často prováděných dotazů.

Připojení HTTP fondu s HttpClientFactory

I když HttpClient implementuje IDisposable rozhraní , je navržené pro opakované použití. Zavřené HttpClient instance necháte sokety ve stavu po krátkou TIME_WAIT dobu otevřené. Pokud se často používá cesta kódu, která vytváří a odstraňuje HttpClient objekty, může aplikace vyčerpaná dostupné sokety. HttpClientFactory byl představen v ASP.NET Core 2,1 jako řešení tohoto problému. Zpracovává připojení HTTP ve fondu za účelem optimalizace výkonu a spolehlivosti.

Recommendations:

Rychlé udržování běžných cest kódu

Chcete, aby byl veškerý kód rychlý. Často volané cesty kódu jsou nejdůležitější pro optimalizaci. Tady jsou některé z nich:

  • Komponenty middlewaru v kanálu zpracování požadavků aplikace, zejména middleware spouštěné včas v kanálu. Tyto součásti mají velký dopad na výkon.
  • Kód, který se spustí pro každý požadavek nebo vícekrát na požadavek. Například vlastní protokolování, obslužné rutiny autorizace nebo inicializace přechodných služeb.

Recommendations:

Dokončení dlouhotrvajících úloh mimo požadavky HTTP

většinu požadavků na aplikaci ASP.NET Core může zpracovat řadič nebo model stránky, který volá potřebné služby a vrací odpověď HTTP. U některých požadavků, které zahrnují dlouhotrvající úlohy, je lepší provést asynchronní zpracování celého procesu požadavků a odpovědí.

Recommendations:

  • Nečekejte na dokončení dlouhotrvajících úloh jako součást běžného zpracování požadavků protokolu HTTP.
  • Vezměte v úvahu zpracování dlouho běžících požadavků se službami na pozadí nebo mimo proces s funkcí Azure Functions. Dokončení práce mimo proces je zvláště užitečné pro úlohy náročné na procesor.
  • SignalR K asynchronní komunikaci s klienty použijte možnosti komunikace v reálném čase, například.

Prostředky klienta minimalizuje

ASP.NET Core aplikace s komplexní front-endy často obsluhují mnoho souborů JavaScript, CSS nebo obrázků. Výkon požadavků na počáteční zatížení může zlepšit:

  • Sdružování, které kombinuje více souborů do jednoho.
  • Minifikace, která zmenšuje velikost souborů odebráním prázdných znaků a komentářů.

Recommendations:

  • použijte pokyny pro sdružování a minifikace, které zmiňují kompatibilní nástroje, a ukazuje, jak používat značku ASP.NET Core environment pro zpracování Development Production prostředí a prostředí.
  • Pro komplexní správu prostředků klienta zvažte další nástroje třetích stran, jako je například Webpack.

Komprimovat odpovědi

Zmenšení velikosti odpovědi obvykle zvyšuje rychlost odezvy aplikace, často výrazně. Jedním ze způsobů, jak omezit velikost datových částí, je komprimace reakcí aplikace. Další informace najdete v tématu odezva komprese.

použít nejnovější verzi ASP.NET Core

každé nové vydání ASP.NET Core zahrnuje vylepšení výkonu. optimalizace v .net Core a ASP.NET Core znamenají, že novější verze obecně překoná starší verze. Například rozhraní .NET Core 2,1 přidalo podporu kompilovaných regulárních výrazů a benefitted z <T> rozsahu. ASP.NET Core 2,2 přidali podporu pro HTTP/2. ASP.NET Core 3,0 přidává mnoho vylepšení , která omezují využití paměti a zvyšují propustnost. Pokud je výkon prioritou, zvažte upgrade na aktuální verzi ASP.NET Core.

Minimalizace výjimek

Výjimky by měly být vzácné. Vyvolávání a zachycování výjimek je pomalé vzhledem k jiným vzorům toku kódu. Z tohoto důvodu by se výjimky neměly používat k řízení normálního toku programu.

Recommendations:

  • Nepoužívejte vyvolání nebo zachycení výjimek jako způsob normálního toku programu, zejména v případě aktivních cest kódu.
  • Do aplikace zahrňte logiku , která zjišťuje a zpracovává podmínky, které by způsobily výjimku.
  • Vyvolejte nebo Zachyťte výjimky pro neobvyklé nebo neočekávané podmínky.

nástroje pro diagnostiku aplikací, jako je například Application Insights, můžou pomáhat identifikovat běžné výjimky v aplikaci, která může mít vliv na výkon.

Výkon a spolehlivost

V následujících částech najdete tipy ke zvýšení výkonu a známé problémy s spolehlivostí a řešení.

Vyhněte se synchronnímu čtení nebo zápisu na HttpRequest/HttpResponse tělo

všechny vstupně-výstupní operace v ASP.NET Core jsou asynchronní. Servery implementují Stream rozhraní, které má synchronní i asynchronní přetížení. Asynchronní objekty by měly být upřednostňovány, aby se zabránilo blokování vláken fondu vláken. Blokování vláken může vést k vyčerpání fondu vláken.

Neprovádět tyto akce: Následující příklad používá ReadToEnd . Zablokuje aktuální vlákno, aby čekal na výsledek. Toto je příklad synchronizace přes Async.

public class BadStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public ActionResult<ContosoData> Get()
    {
        var json = new StreamReader(Request.Body).ReadToEnd();

        return JsonSerializer.Deserialize<ContosoData>(json);
    }
}

V předchozím kódu Get synchronně přečte celý text požadavku HTTP do paměti. Pokud se klient pomalu odesílá, aplikace provádí synchronizaci přes Async. Aplikace provádí synchronizaci přes Async, protože Kestrel nepodporuje synchronní čtení.

Postupujte takto: Následující příklad používá ReadToEndAsync a neblokuje vlákno při čtení.

public class GoodStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public async Task<ActionResult<ContosoData>> Get()
    {
        var json = await new StreamReader(Request.Body).ReadToEndAsync();

        return JsonSerializer.Deserialize<ContosoData>(json);
    }

}

Předchozí kód asynchronně načte celý text požadavku HTTP do paměti.

Upozornění

Pokud je žádost velká, čtení celého textu požadavku HTTP do paměti by mohlo vést k nedostatku paměti (OOM). OOM může mít za následek odepření služby. Další informace najdete v tématu zamezení čtení velkých těla žádostí nebo těla reakcí do paměti v tomto dokumentu.

Postupujte takto: Následující příklad je plně asynchronní pomocí textu žádosti bez vyrovnávací paměti:

public class GoodStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public async Task<ActionResult<ContosoData>> Get()
    {
        return await JsonSerializer.DeserializeAsync<ContosoData>(Request.Body);
    }
}

Předchozí kód asynchronně deserializace tělo požadavku do objektu jazyka C#.

Preferovat ReadFormAsync přes Request. Form

HttpContext.Request.ReadFormAsyncMísto použijte HttpContext.Request.Form . HttpContext.Request.Form lze bezpečně číst pouze s následujícími podmínkami:

  • Formulář byl přečten voláním ReadFormAsync a
  • Hodnota formuláře v mezipaměti je čtena pomocí HttpContext.Request.Form

Neprovádět tyto akce: Následující příklad používá HttpContext.Request.Form . HttpContext.Request.Form používá synchronizaci přes Async a může vést k vyčerpání fondu vláken.

public class BadReadController : Controller
{
    [HttpPost("/form-body")]
    public IActionResult Post()
    {
        var form =  HttpContext.Request.Form;

        Process(form["id"], form["name"]);

        return Accepted();
    }

Postupujte takto: Následující příklad používá HttpContext.Request.ReadFormAsync pro asynchronní čtení těla formuláře.

public class GoodReadController : Controller
{
    [HttpPost("/form-body")]
    public async Task<IActionResult> Post()
    {
       var form = await HttpContext.Request.ReadFormAsync();

        Process(form["id"], form["name"]);

        return Accepted();
    }

Vyhněte se čtení velkých těla žádostí nebo těla reakcí do paměti

V rozhraní .NET končí každá alokace objektu větší než 85 KB v haldě velkých objektů (LOH). Velké objekty jsou nákladné dvěma způsoby:

  • Náklady na přidělení jsou vysoké, protože paměť pro nově přidělený velký objekt musí být smazána. Modul CLR garantuje, že paměť pro všechny nově přidělené objekty jsou vymazány.
  • LOH se shromáždí se zbytkem haldy. LOH vyžaduje úplné uvolňování paměti nebo kolekci Gen2.

Tento Blogový příspěvek popisuje stručně problém:

Když je přidělen velký objekt, je označen jako objekt Gen 2. Nejedná se o gen 0 jako u malých objektů. Důsledkem je, že pokud vyčerpáte paměť v LOH, UVOLŇOVÁNí paměti vyčistí celou spravovanou haldu, nejen LOH. Proto vyčistí obecné 0, obecné 1 a obecné 2 včetně LOH. Nazývá se to úplné uvolňování paměti a je to nejvíce časově náročné uvolňování paměti. U mnoha aplikací může být přijatelné. Ale pro vysoce výkonné webové servery, u kterých je potřeba pár paměťových vyrovnávacích pamětí potřebných ke zpracování průměrného webového požadavku (čtení z soketu, dekomprese, dekódování JSON & více), ale opravdu ne.

Naively ukládání velkého textu žádosti nebo odpovědi do jednoho nebo více byte[] string :

  • Může způsobit, že dojde k rychlému vyzkoušení volného místa v LOH.
  • Může způsobit problémy s výkonem aplikace z důvodu úplného GC běhu.

Práce s rozhraním API pro synchronní zpracování dat

Při použití serializátoru/zrušení serializátoru, který podporuje pouze synchronní čtení a zápisy (například JSON.NET):

  • Ukládat data do vyrovnávací paměti asynchronně předtím, než je předáte do serializátoru/zrušení serializátoru.

Upozornění

Pokud je žádost velká, může to vést k nedostatku paměti (OOM). OOM může mít za následek odepření služby. Další informace najdete v tématu zamezení čtení velkých těla žádostí nebo těla reakcí do paměti v tomto dokumentu.

ASP.NET Core 3,0 používá System.Text.Json ve výchozím nastavení pro serializaci JSON. System.Text.Json:

  • Čte a zapisuje JSON asynchronně.
  • Je optimalizován pro text v kódování UTF-8.
  • Obvykle vyšší výkon než Newtonsoft.Json .

Do pole neukládejte IHttpContextAccessor. HttpContext.

IHttpContextAccessor. HttpContext vrací HttpContext aktivní požadavek při přístupu z vlákna požadavku. IHttpContextAccessor.HttpContextNeměl by být uložen v poli nebo proměnné.

Neprovádět tyto akce: V následujícím příkladu je uloženo HttpContext v poli a pak se pokusí o pozdější použití.

public class MyBadType
{
    private readonly HttpContext _context;
    public MyBadType(IHttpContextAccessor accessor)
    {
        _context = accessor.HttpContext;
    }

    public void CheckAdmin()
    {
        if (!_context.User.IsInRole("admin"))
        {
            throw new UnauthorizedAccessException("The current user isn't an admin");
        }
    }
}

Předchozí kód často zachycuje v konstruktoru hodnotu null nebo není správný HttpContext .

Postupujte takto: Následující příklad:

  • Uloží pole IHttpContextAccessor do pole.
  • Používá HttpContext pole ve správné době a kontroluje pro null .
public class MyGoodType
{
    private readonly IHttpContextAccessor _accessor;
    public MyGoodType(IHttpContextAccessor accessor)
    {
        _accessor = accessor;
    }

    public void CheckAdmin()
    {
        var context = _accessor.HttpContext;
        if (context != null && !context.User.IsInRole("admin"))
        {
            throw new UnauthorizedAccessException("The current user isn't an admin");
        }
    }
}

Nepřistupovat k HttpContext z více vláken

HttpContext není bezpečná pro přístup z více vláken . Přístup HttpContext z více vláken paralelně může způsobit nedefinované chování, jako je například zablokování, zhroucení a poškození dat.

Neprovádět tyto akce: Následující příklad vytvoří tři paralelní požadavky a zaznamená příchozí cestu požadavku před a za odchozí požadavek HTTP. Cesta k požadavku je k dispozici z více vláken, potenciálně paralelně.

public class AsyncBadSearchController : Controller
{       
    [HttpGet("/search")]
    public async Task<SearchResults> Get(string query)
    {
        var query1 = SearchAsync(SearchEngine.Google, query);
        var query2 = SearchAsync(SearchEngine.Bing, query);
        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query);

        await Task.WhenAll(query1, query2, query3);

        var results1 = await query1;
        var results2 = await query2;
        var results3 = await query3;

        return SearchResults.Combine(results1, results2, results3);
    }       

    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query)
    {
        var searchResults = _searchService.Empty();
        try
        {
            _logger.LogInformation("Starting search query from {path}.", 
                                    HttpContext.Request.Path);
            searchResults = _searchService.Search(engine, query);
            _logger.LogInformation("Finishing search query from {path}.", 
                                    HttpContext.Request.Path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed query from {path}", 
                             HttpContext.Request.Path);
        }

        return await searchResults;
    }

Postupujte takto: Následující příklad kopíruje všechna data z příchozího požadavku a teprve potom provádí tři paralelní požadavky.

public class AsyncGoodSearchController : Controller
{       
    [HttpGet("/search")]
    public async Task<SearchResults> Get(string query)
    {
        string path = HttpContext.Request.Path;
        var query1 = SearchAsync(SearchEngine.Google, query,
                                 path);
        var query2 = SearchAsync(SearchEngine.Bing, query, path);
        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path);

        await Task.WhenAll(query1, query2, query3);

        var results1 = await query1;
        var results2 = await query2;
        var results3 = await query3;

        return SearchResults.Combine(results1, results2, results3);
    }

    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query,
                                                  string path)
    {
        var searchResults = _searchService.Empty();
        try
        {
            _logger.LogInformation("Starting search query from {path}.",
                                   path);
            searchResults = await _searchService.SearchAsync(engine, query);
            _logger.LogInformation("Finishing search query from {path}.", path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed query from {path}", path);
        }

        return await searchResults;
    }

Nepoužívat HttpContext po dokončení žádosti

HttpContextje platná pouze tak dlouho, dokud je v kanálu ASP.NET Core aktivní požadavek HTTP. celý kanál ASP.NET Core je asynchronní řetěz delegátů, který provádí všechny požadavky. Po Task dokončení operace vrácené z tohoto řetězu HttpContext se recykluje.

Neprovádět tyto akce: Následující příklad používá, aby async void byl požadavek HTTP dokončen při prvním dosažení prvního await :

  • což je vždy špatný postup v aplikacích ASP.NET Core.
  • Přístup k HttpResponse po dokončení požadavku HTTP.
  • Dojde k chybě procesu.
public class AsyncBadVoidController : Controller
{
    [HttpGet("/async")]
    public async void Get()
    {
        await Task.Delay(1000);

        // The following line will crash the process because of writing after the 
        // response has completed on a background thread. Notice async void Get()

        await Response.WriteAsync("Hello World");
    }
}

Postupujte takto: Následující příklad vrátí Task rozhraní, takže požadavek HTTP nebude dokončen, dokud se akce nedokončí.

public class AsyncGoodTaskController : Controller
{
    [HttpGet("/async")]
    public async Task Get()
    {
        await Task.Delay(1000);

        await Response.WriteAsync("Hello World");
    }
}

Nezachytit vlastnost HttpContext v vláknech na pozadí

Neprovádět tyto akce: Následující příklad ukazuje, že uzavření zachytí HttpContext z Controller Vlastnosti. Toto je špatný postup, protože pracovní položka by mohla:

  • Spustit mimo rozsah požadavku.
  • Došlo k pokusu o čtení špatného HttpContext .
[HttpGet("/fire-and-forget-1")]
public IActionResult BadFireAndForget()
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        var path = HttpContext.Request.Path;
        Log(path);
    });

    return Accepted();
}

Postupujte takto: Následující příklad:

  • Zkopíruje data potřebná v úloze na pozadí během žádosti.
  • Neodkazuje na cokoli z kontroleru.
[HttpGet("/fire-and-forget-3")]
public IActionResult GoodFireAndForget()
{
    string path = HttpContext.Request.Path;
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        Log(path);
    });

    return Accepted();
}

Úlohy na pozadí by se měly implementovat jako hostované služby. Další informace najdete v tématu úlohy na pozadí s hostovanými službami.

Nezachytávat služby vložené do řadičů v vláknech na pozadí

Neprovádět tyto akce: Následující příklad ukazuje, že uzavření zachytí DbContext z Controller parametru action. Toto je špatný postup. Pracovní položka by mohla běžet mimo rozsah požadavku. ContosoDbContextJe vymezen na žádost, výsledkem je ObjectDisposedException .

[HttpGet("/fire-and-forget-1")]
public IActionResult FireAndForget1([FromServices]ContosoDbContext context)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        context.Contoso.Add(new Contoso());
        await context.SaveChangesAsync();
    });

    return Accepted();
}

Postupujte takto: Následující příklad:

  • Vloží objekt IServiceScopeFactory , aby bylo možné vytvořit obor v pracovní položce na pozadí. IServiceScopeFactory je typu singleton.
  • Vytvoří nový rozsah vkládání závislostí ve vlákně na pozadí.
  • Neodkazuje na cokoli z kontroleru.
  • Nezachycuje ContosoDbContext z příchozího požadavku.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory 
                                    serviceScopeFactory)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceScopeFactory.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();

            context.Contoso.Add(new Contoso());

            await context.SaveChangesAsync();                                        
        }
    });

    return Accepted();
}

Následující zvýrazněný kód:

  • Vytvoří obor pro dobu života operace na pozadí a vyřeší z něj služby.
  • Používá ContosoDbContext se správným oborem.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory 
                                    serviceScopeFactory)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceScopeFactory.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();

            context.Contoso.Add(new Contoso());

            await context.SaveChangesAsync();                                        
        }
    });

    return Accepted();
}

Neměňte stavový kód ani hlavičky po zahájení textu odpovědi

ASP.NET Core neukládá obsah odpovědi HTTP do vyrovnávací paměti. Při prvním zápisu odpovědi:

  • Hlavičky jsou odesílány spolu s tímto blokem těla do klienta.
  • Již není možné měnit hlavičky odpovědí.

Neprovádět tyto akce: Následující kód se pokusí přidat hlavičky odpovědi poté, co byla odpověď již spuštěna:

app.Use(async (context, next) =>
{
    await next();

    context.Response.Headers["test"] = "test value";
});

V předchozím kódu context.Response.Headers["test"] = "test value"; vyvolá výjimku, pokud next() zapisuje do odpovědi.

Postupujte takto: Následující příklad zkontroluje, zda byla před úpravou hlaviček spuštěna odpověď protokolu HTTP.

app.Use(async (context, next) =>
{
    await next();

    if (!context.Response.HasStarted)
    {
        context.Response.Headers["test"] = "test value";
    }
});

Postupujte takto: Následující příklad používá HttpResponse.OnStarting k nastavení hlaviček před vyprázdněním hlaviček odpovědí na klienta.

Kontrola, zda odpověď nezačala, umožňuje registraci zpětného volání, které bude vyvoláno těsně před zápisem hlaviček odpovědí. Kontroluje se, jestli odpověď nezačala:

  • Poskytuje možnost připojit nebo přepsat hlavičky v čase.
  • Nevyžaduje znalosti dalšího middlewaru v kanálu.
app.Use(async (context, next) =>
{
    context.Response.OnStarting(() =>
    {
        context.Response.Headers["someheader"] = "somevalue";
        return Task.CompletedTask;
    });

    await next();
});

Nevolejte Next (), pokud jste již začali zapisovat do těla odpovědi

Součásti, které mají být volány, jsou očekávány pouze v případě, že je možné je zpracovat a manipulovat s ní.

Použití vnitroprocesového hostování se službou IIS

při použití hostování v rámci procesu ASP.NET Core aplikace běží ve stejném procesu jako jeho pracovní proces služby IIS. Hostování v rámci procesů poskytují lepší výkon než hostování mimo procesy, protože požadavky nejsou proxy serverem přes adaptér zpětné smyčky. Adaptér zpětné smyčky je síťové rozhraní, které vrátí odchozí síťový provoz zpátky do stejného počítače. služba IIS zpracovává správu procesů pomocí služby was (Windows process Activation Service).

projekty jsou ve výchozím nastavení pro model hostování v rámci procesu v ASP.NET Core 3,0 a novějším.

další informace najdete v tématu hostitelská ASP.NET Core v Windows se službou IIS .