Buforowanie w pamięci w ASP.NET Core

Autorstwa Ricka Andersona, Johna Luo i Steve'a Smitha

Buforowanie może znacząco poprawić wydajność i skalowalność aplikacji, zmniejszając pracę wymaganą do wygenerowania zawartości. Buforowanie najlepiej sprawdza się w przypadku danych, które zmieniają się rzadko i są kosztowne do wygenerowania. Buforowanie tworzy kopię danych, które mogą być zwracane znacznie szybciej niż ze źródła. Aplikacje powinny być zapisywane i testowane, aby nigdy nie zależeć od danych w pamięci podręcznej.

ASP.NET Core obsługuje kilka różnych pamięci podręcznych. Najprostsza pamięć podręczna jest oparta na .IMemoryCache IMemoryCache reprezentuje pamięć podręczną przechowywaną w pamięci serwera internetowego. Aplikacje działające na farmie serwerów (wiele serwerów) powinny zapewnić, że sesje są lepkie podczas korzystania z pamięci podręcznej w pamięci. Sesje sticky zapewniają, że żądania od klienta przechodzą do tego samego serwera. Na przykład aplikacje internetowe platformy Azure używają routingu żądań aplikacji (ARR) do kierowania wszystkich żądań do tego samego serwera.

Sesje nieklejne w farmie sieci Web wymagają rozproszonej pamięci podręcznej , aby uniknąć problemów ze spójnością pamięci podręcznej. W przypadku niektórych aplikacji rozproszona pamięć podręczna może obsługiwać wyższe skalowanie w poziomie niż pamięć podręczna w pamięci. Użycie rozproszonej pamięci podręcznej odciąża pamięć podręczną do procesu zewnętrznego.

Pamięć podręczna w pamięci może przechowywać dowolny obiekt. Interfejs rozproszonej pamięci podręcznej jest ograniczony do byte[]. Elementy pamięci podręcznej w pamięci i rozproszonej pamięci podręcznej są przechowywane jako pary klucz-wartość.

System.Runtime. Buforowanie/PamięćCache

System.Runtime.Caching/MemoryCache (Pakiet NuGet) może być używany z:

  • .NET Standard 2.0 lub nowszy.
  • Każda implementacja platformy .NET przeznaczona dla platformy .NET Standard 2.0 lub nowszej. Na przykład ASP.NET Core 3.1 lub nowszym.
  • .NET Framework 4.5 lub nowszy.

Microsoft.Extensions. Buforowanie. Pamięć/IMemoryCache (opisana w tym artykule) jest zalecanaSystem.Runtime.Caching/MemoryCache, ponieważ jest lepiej zintegrowana z platformą ASP.NET Core. Na przykład IMemoryCache działa natywnie z iniekcją zależności ASP.NET Core.

Użyj System.Runtime.Caching/MemoryCache jako mostka zgodności podczas przenoszenia kodu z ASP.NET 4.x do ASP.NET Core.

Wskazówki dotyczące pamięci podręcznej

  • Kod powinien zawsze mieć opcję rezerwową pobierania danych i nie zależy od dostępnej buforowanej wartości.
  • Pamięć podręczna używa ograniczonego zasobu, pamięci. Ogranicz wzrost pamięci podręcznej:
    • Nie należy wstawiać zewnętrznych danych wejściowych do pamięci podręcznej. Na przykład użycie dowolnych danych wejściowych dostarczonych przez użytkownika jako klucza pamięci podręcznej nie jest zalecane, ponieważ dane wejściowe mogą zużywać nieprzewidywalną ilość pamięci.
    • Użyj wygasań, aby ograniczyć wzrost pamięci podręcznej.
    • Użyj polecenia SetSize, Size i SizeLimit, aby ograniczyć rozmiar pamięci podręcznej. Środowisko uruchomieniowe ASP.NET Core nie ogranicza rozmiaru pamięci podręcznej na podstawie ciśnienia pamięci. Deweloper może ograniczyć rozmiar pamięci podręcznej.

Korzystanie z usługi IMemoryCache

Ostrzeżenie

Użycie pamięci udostępnionej pamięci podręcznej z iniekcji zależności i wywołanie SetSizemetody , Sizelub SizeLimit ograniczenie rozmiaru pamięci podręcznej może spowodować niepowodzenie aplikacji. Po ustawieniu limitu rozmiaru w pamięci podręcznej wszystkie wpisy muszą określać rozmiar podczas dodawania. Może to prowadzić do problemów, ponieważ deweloperzy mogą nie mieć pełnej kontroli nad tym, co korzysta z udostępnionej pamięci podręcznej. W przypadku używania metody SetSize, Sizelub SizeLimit w celu ograniczenia pamięci podręcznej utwórz pojedynczyton pamięci podręcznej na potrzeby buforowania. Aby uzyskać więcej informacji i przykład, zobacz Używanie polecenia SetSize, Size i SizeLimit w celu ograniczenia rozmiaru pamięci podręcznej. Udostępniona pamięć podręczna jest jedną współdzieloną przez inne struktury lub biblioteki.

Buforowanie w pamięci to usługa , do którego odwołuje się aplikacja przy użyciu iniekcji zależności. Zażądaj IMemoryCache wystąpienia w konstruktorze:

public class IndexModel : PageModel
{
    private readonly IMemoryCache _memoryCache;

    public IndexModel(IMemoryCache memoryCache) =>
        _memoryCache = memoryCache;

    // ...

Poniższy kod służy TryGetValue do sprawdzania, czy w pamięci podręcznej znajduje się czas. Jeśli czas nie jest buforowany, zostanie utworzony nowy wpis i dodany do pamięci podręcznej za pomocą polecenia Set:

public void OnGet()
{
    CurrentDateTime = DateTime.Now;

    if (!_memoryCache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
    {
        cacheValue = CurrentDateTime;

        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        _memoryCache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
    }

    CacheCurrentDateTime = cacheValue;
}

W poprzednim kodzie wpis pamięci podręcznej jest skonfigurowany z przesuwanym wygaśnięciem trzech sekund. Jeśli dostęp do wpisu pamięci podręcznej nie jest uzyskiwany przez więcej niż trzy sekundy, zostanie wykluczony z pamięci podręcznej. Za każdym razem, gdy dostęp do wpisu pamięci podręcznej jest uzyskiwany, pozostaje w pamięci podręcznej przez kolejne 3 sekundy. Klasa CacheKeys jest częścią przykładu pobierania.

Zostanie wyświetlony bieżący czas i czas buforowany:

<ul>
    <li>Current Time: @Model.CurrentDateTime</li>
    <li>Cached Time: @Model.CacheCurrentDateTime</li>
</ul>

Poniższy kod używa Set metody rozszerzenia do buforowania danych przez względny czas bez MemoryCacheEntryOptions:

_memoryCache.Set(CacheKeys.Entry, DateTime.Now, TimeSpan.FromDays(1));

W poprzednim kodzie wpis pamięci podręcznej jest skonfigurowany z względnym wygaśnięciem jednego dnia. Wpis pamięci podręcznej jest eksmitowany z pamięci podręcznej po jednym dniu, nawet jeśli jest on dostępny w tym przedziale czasu.

Poniższy kod używa instrukcji GetOrCreate i GetOrCreateAsync do buforowania danych.

public void OnGetCacheGetOrCreate()
{
    var cachedValue = _memoryCache.GetOrCreate(
        CacheKeys.Entry,
        cacheEntry =>
        {
            cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
            return DateTime.Now;
        });

    // ...
}

public async Task OnGetCacheGetOrCreateAsync()
{
    var cachedValue = await _memoryCache.GetOrCreateAsync(
        CacheKeys.Entry,
        cacheEntry =>
        {
            cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
            return Task.FromResult(DateTime.Now);
        });

    // ...
}

Następujące wywołania Get kodu w celu pobrania buforowanego czasu:

var cacheEntry = _memoryCache.Get<DateTime?>(CacheKeys.Entry);

Poniższy kod pobiera lub tworzy buforowany element z bezwzględnym wygaśnięciem:

var cachedValue = _memoryCache.GetOrCreate(
    CacheKeys.Entry,
    cacheEntry =>
    {
        cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

Buforowany zestaw elementów z tylko przesuwanym wygaśnięciem jest narażony na nigdy nie wygasanie. Jeśli buforowany element jest wielokrotnie uzyskiwany w przedziale czasu wygaśnięcia, element nigdy nie wygaśnie. Połącz przesuwane wygaśnięcie z bezwzględnym wygaśnięciem, aby zagwarantować wygaśnięcie elementu. Bezwzględne wygaśnięcie ustawia górną granicę czasu buforowania elementu, jednocześnie zezwalając na wygaśnięcie elementu wcześniej, jeśli nie jest on żądany w interwale wygasania. Jeśli interwał wygasania przesuwanego lub bezwzględny czas wygaśnięcia, element zostanie wykluczony z pamięci podręcznej.

Poniższy kod pobiera lub tworzy buforowany element z funkcją przesuwania i wygaśnięcia bezwzględnego:

var cachedValue = _memoryCache.GetOrCreate(
    CacheKeys.CallbackEntry,
    cacheEntry =>
    {
        cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
        cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

Powyższy kod gwarantuje, że dane nie będą buforowane dłużej niż bezwzględny czas.

GetOrCreate, GetOrCreateAsynci Get są metodami rozszerzeń w CacheExtensions klasie . Te metody rozszerzają IMemoryCachemożliwości programu .

MemoryCacheEntryOptions

Poniższy przykład:

  • Ustawia priorytet pamięci podręcznej na CacheItemPriority.NeverRemove.
  • Ustawia element PostEvictionDelegate , który jest wywoływany po eksmitowanym wpisie z pamięci podręcznej. Wywołanie zwrotne jest uruchamiane w innym wątku niż kod, który usuwa element z pamięci podręcznej.
public void OnGetCacheRegisterPostEvictionCallback()
{
    var memoryCacheEntryOptions = new MemoryCacheEntryOptions()
        .SetPriority(CacheItemPriority.NeverRemove)
        .RegisterPostEvictionCallback(PostEvictionCallback, _memoryCache);

    _memoryCache.Set(CacheKeys.CallbackEntry, DateTime.Now, memoryCacheEntryOptions);
}

private static void PostEvictionCallback(
    object cacheKey, object cacheValue, EvictionReason evictionReason, object state)
{
    var memoryCache = (IMemoryCache)state;

    memoryCache.Set(
        CacheKeys.CallbackMessage,
        $"Entry {cacheKey} was evicted: {evictionReason}.");
}

Użyj polecenia SetSize, Size i SizeLimit, aby ograniczyć rozmiar pamięci podręcznej

Wystąpienie MemoryCache może opcjonalnie określić i wymusić limit rozmiaru. Limit rozmiaru pamięci podręcznej nie ma zdefiniowanej jednostki miary, ponieważ pamięć podręczna nie ma mechanizmu mierzenia rozmiaru wpisów. Jeśli ustawiono limit rozmiaru pamięci podręcznej, wszystkie wpisy muszą określać rozmiar. Środowisko uruchomieniowe ASP.NET Core nie ogranicza rozmiaru pamięci podręcznej na podstawie ciśnienia pamięci. Deweloper może ograniczyć rozmiar pamięci podręcznej. Określony rozmiar znajduje się w jednostkach wybieranych przez dewelopera.

Na przykład:

  • Jeśli aplikacja internetowa była głównie ciągami buforowania, każdy rozmiar wpisu pamięci podręcznej może być długością ciągu.
  • Aplikacja może określić rozmiar wszystkich wpisów jako 1, a limit rozmiaru to liczba wpisów.

Jeśli SizeLimit nie jest ustawiona, pamięć podręczna rośnie bez ograniczenia. Środowisko uruchomieniowe ASP.NET Core nie przycina pamięci podręcznej, gdy pamięć systemowa jest niska. Aplikacje muszą być zaprojektowane tak, aby:

  • Ogranicz wzrost pamięci podręcznej.
  • Wywołanie Compact lub Remove gdy dostępna pamięć jest ograniczona.

Poniższy kod tworzy bezjednostki stały rozmiar MemoryCache dostępny przez iniekcję zależności:

public class MyMemoryCache
{
    public MemoryCache Cache { get; } = new MemoryCache(
        new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
}

SizeLimit nie ma jednostek. Buforowane wpisy muszą określać rozmiar niezależnie od jednostek, które uważają za najbardziej odpowiednie, jeśli został ustawiony limit rozmiaru pamięci podręcznej. Wszyscy użytkownicy wystąpienia pamięci podręcznej powinni używać tego samego systemu jednostkowego. Wpis nie będzie buforowany, jeśli suma buforowanych rozmiarów wpisu przekracza wartość określoną przez SizeLimit. Jeśli nie ustawiono limitu rozmiaru pamięci podręcznej, rozmiar pamięci podręcznej ustawiony na wpis jest ignorowany.

Poniższy kod rejestruje się MyMemoryCache w kontenerze wstrzykiwania zależności:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddSingleton<MyMemoryCache>();

MyMemoryCache Jest tworzony jako niezależna pamięć podręczna dla składników, które mają świadomość tego rozmiaru ograniczonej pamięci podręcznej i wiedzą, jak odpowiednio ustawić rozmiar wpisu pamięci podręcznej.

Rozmiar wpisu pamięci podręcznej można ustawić przy użyciu SetSize metody rozszerzenia lub Size właściwości:

if (!_myMemoryCache.Cache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
{
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        .SetSize(1);

    // cacheEntryOptions.Size = 1;

    _myMemoryCache.Cache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
}

W poprzednim kodzie dwa wyróżnione wiersze osiągają ten sam wynik ustawiania rozmiaru wpisu pamięci podręcznej. SetSize jest zapewniana dla wygody podczas łączenia połączeń na new MemoryCacheOptions().

MemoryCache.Compact

MemoryCache.Compact próbuje usunąć określony procent pamięci podręcznej w następującej kolejności:

  • Wszystkie wygasłe elementy.
  • Elementy według priorytetu. Elementy o najniższym priorytecie są najpierw usuwane.
  • Najmniej ostatnio używane obiekty.
  • Elementy z najwcześniejszym bezwzględnym wygaśnięciem.
  • Elementy z najwcześniejszym przesuwanym wygaśnięciem.

Przypięte elementy o priorytecie NeverRemove nigdy nie są usuwane. Poniższy kod usuwa element pamięci podręcznej i wywołuje polecenie Compact , aby usunąć 25% buforowanych wpisów:

_myMemoryCache.Cache.Remove(CacheKeys.Entry);
_myMemoryCache.Cache.Compact(.25);

Aby uzyskać więcej informacji, zobacz Compact source on GitHub (Kompaktowanie źródła w usłudze GitHub).

Zależności pamięci podręcznej

W poniższym przykładzie pokazano, jak wygasnąć wpis pamięci podręcznej, jeśli wpis zależny wygaśnie. Element jest CancellationChangeToken dodawany do buforowanego elementu. Po Cancel wywołaniu w obiekcie CancellationTokenSourceoba wpisy pamięci podręcznej są eksmitowane:

public void OnGetCacheCreateDependent()
{
    var cancellationTokenSource = new CancellationTokenSource();

    _memoryCache.Set(
        CacheKeys.DependentCancellationTokenSource,
        cancellationTokenSource);

    using var parentCacheEntry = _memoryCache.CreateEntry(CacheKeys.Parent);

    parentCacheEntry.Value = DateTime.Now;

    _memoryCache.Set(
        CacheKeys.Child,
        DateTime.Now,
        new CancellationChangeToken(cancellationTokenSource.Token));
}

public void OnGetCacheRemoveDependent()
{
    var cancellationTokenSource = _memoryCache.Get<CancellationTokenSource>(
        CacheKeys.DependentCancellationTokenSource);

    cancellationTokenSource.Cancel();
}

Użycie elementu CancellationTokenSource umożliwia eksmitowanie wielu wpisów pamięci podręcznej jako grupy. using We wzorcu w powyższym kodzie wpisy pamięci podręcznej utworzone wewnątrz using zakresu dziedziczą wyzwalacze i ustawienia wygasania.

Dodatkowe uwagi

  • Wygaśnięcie nie jest wykonywane w tle. Nie ma czasomierza, który aktywnie skanuje pamięć podręczną pod kątem wygasłych elementów. Każde działanie w pamięci podręcznej (Get, Set, Remove) może wyzwolić skanowanie w tle pod kątem wygasłych elementów. Czasomierz w elemecie CancellationTokenSource (CancelAfter) usuwa również wpis i wyzwala skanowanie pod kątem wygasłych elementów. W poniższym przykładzie użyto CancellationTokenSource(TimeSpan) dla zarejestrowanego tokenu. Gdy ten token zostanie wyzwolony, natychmiast usunie wpis i uruchomi wywołania zwrotne eksmisji:

    if (!_memoryCache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
    {
        cacheValue = DateTime.Now;
    
        var cancellationTokenSource = new CancellationTokenSource(
            TimeSpan.FromSeconds(10));
    
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .AddExpirationToken(
                new CancellationChangeToken(cancellationTokenSource.Token))
            .RegisterPostEvictionCallback((key, value, reason, state) =>
            {
                ((CancellationTokenSource)state).Dispose();
            }, cancellationTokenSource);
    
        _memoryCache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
    }
    
  • W przypadku ponownego wypełniania elementu pamięci podręcznej przy użyciu wywołania zwrotnego:

    • Wiele żądań może znaleźć wartość klucza buforowanego pustą, ponieważ wywołanie zwrotne nie zostało ukończone.
    • Może to spowodować ponowne wypełnienie buforowanego elementu kilkoma wątkami.
  • Gdy jeden wpis pamięci podręcznej jest używany do utworzenia innego, element podrzędny kopiuje tokeny wygasania wpisu nadrzędnego i ustawienia wygasania na podstawie czasu. Element podrzędny nie wygasł przez ręczne usunięcie ani zaktualizowanie wpisu nadrzędnego.

  • Służy PostEvictionCallbacks do ustawiania wywołań zwrotnych, które zostaną wyzwolone po eksmitowaniu wpisu pamięci podręcznej z pamięci podręcznej.

  • W przypadku większości aplikacji IMemoryCache jest włączona. Na przykład wywoływanie AddMvcmetod , , AddControllersWithViews, AddRazorPagesAddMvcCore().AddRazorViewEnginei wielu innych Add{Service} metod w systemie Program.csumożliwia .IMemoryCache W przypadku aplikacji, które nie wywołają jednej z powyższych Add{Service} metod, może być konieczne wywołanie AddMemoryCache metody w pliku Program.cs.

Aktualizacja pamięci podręcznej w tle

Użyj usługi w tle, takiej jak IHostedService , aby zaktualizować pamięć podręczną. Usługa w tle może ponownie skompilować wpisy, a następnie przypisać je do pamięci podręcznej tylko wtedy, gdy są gotowe.

Dodatkowe zasoby

Wyświetl lub pobierz przykładowy kod (jak pobrać)

podstawy Buforowanie

Buforowanie może znacząco poprawić wydajność i skalowalność aplikacji, zmniejszając pracę wymaganą do wygenerowania zawartości. Buforowanie najlepiej sprawdza się w przypadku danych, które zmieniają się rzadko i są kosztowne do wygenerowania. Buforowanie tworzy kopię danych, które mogą być zwracane znacznie szybciej niż ze źródła. Aplikacje powinny być zapisywane i testowane, aby nigdy nie zależeć od danych w pamięci podręcznej.

ASP.NET Core obsługuje kilka różnych pamięci podręcznych. Najprostsza pamięć podręczna jest oparta na .IMemoryCache IMemoryCache reprezentuje pamięć podręczną przechowywaną w pamięci serwera internetowego. Aplikacje działające na farmie serwerów (wiele serwerów) powinny zapewnić, że sesje są lepkie podczas korzystania z pamięci podręcznej w pamięci. Sesje sticky zapewniają, że kolejne żądania od klienta przechodzą do tego samego serwera. Na przykład aplikacje internetowe platformy Azure używają routingu żądań aplikacji (ARR) do kierowania wszystkich kolejnych żądań do tego samego serwera.

Sesje nieklejne w farmie sieci Web wymagają rozproszonej pamięci podręcznej , aby uniknąć problemów ze spójnością pamięci podręcznej. W przypadku niektórych aplikacji rozproszona pamięć podręczna może obsługiwać wyższe skalowanie w poziomie niż pamięć podręczna w pamięci. Użycie rozproszonej pamięci podręcznej odciąża pamięć podręczną do procesu zewnętrznego.

Pamięć podręczna w pamięci może przechowywać dowolny obiekt. Interfejs rozproszonej pamięci podręcznej jest ograniczony do byte[]. Elementy pamięci podręcznej w pamięci i rozproszonej pamięci podręcznej są przechowywane jako pary klucz-wartość.

System.Runtime. Buforowanie/PamięćCache

System.Runtime.Caching/MemoryCache (Pakiet NuGet) może być używany z:

  • .NET Standard 2.0 lub nowszy.
  • Każda implementacja platformy .NET przeznaczona dla platformy .NET Standard 2.0 lub nowszej. Na przykład ASP.NET Core 3.1 lub nowszym.
  • .NET Framework 4.5 lub nowszy.

Microsoft.Extensions. Buforowanie. Pamięć/IMemoryCache (opisana w tym artykule) jest zalecanaSystem.Runtime.Caching/MemoryCache, ponieważ jest lepiej zintegrowana z platformą ASP.NET Core. Na przykład IMemoryCache działa natywnie z iniekcją zależności ASP.NET Core.

Użyj System.Runtime.Caching/MemoryCache jako mostka zgodności podczas przenoszenia kodu z ASP.NET 4.x do ASP.NET Core.

Wskazówki dotyczące pamięci podręcznej

  • Kod powinien zawsze mieć opcję rezerwową pobierania danych i nie zależy od dostępnej buforowanej wartości.
  • Pamięć podręczna używa ograniczonego zasobu, pamięci. Ogranicz wzrost pamięci podręcznej:
    • Nie używaj zewnętrznych danych wejściowych jako kluczy pamięci podręcznej.
    • Użyj wygasań, aby ograniczyć wzrost pamięci podręcznej.
    • Użyj polecenia SetSize, Size i SizeLimit, aby ograniczyć rozmiar pamięci podręcznej. Środowisko uruchomieniowe ASP.NET Core nie ogranicza rozmiaru pamięci podręcznej na podstawie ciśnienia pamięci. Deweloper może ograniczyć rozmiar pamięci podręcznej.

Korzystanie z usługi IMemoryCache

Ostrzeżenie

Użycie pamięci udostępnionej pamięci podręcznej z iniekcji zależności i wywołanie SetSizemetody , Sizelub SizeLimit ograniczenie rozmiaru pamięci podręcznej może spowodować niepowodzenie aplikacji. Po ustawieniu limitu rozmiaru w pamięci podręcznej wszystkie wpisy muszą określać rozmiar podczas dodawania. Może to prowadzić do problemów, ponieważ deweloperzy mogą nie mieć pełnej kontroli nad tym, co korzysta z udostępnionej pamięci podręcznej. W przypadku używania metody SetSize, Sizelub SizeLimit w celu ograniczenia pamięci podręcznej utwórz pojedynczyton pamięci podręcznej na potrzeby buforowania. Aby uzyskać więcej informacji i przykład, zobacz Używanie polecenia SetSize, Size i SizeLimit w celu ograniczenia rozmiaru pamięci podręcznej. Udostępniona pamięć podręczna jest jedną współdzieloną przez inne struktury lub biblioteki.

Buforowanie w pamięci to usługa , do którego odwołuje się aplikacja przy użyciu iniekcji zależności. Zażądaj IMemoryCache wystąpienia w konstruktorze:

public class HomeController : Controller
{
    private IMemoryCache _cache;

    public HomeController(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }

Poniższy kod służy TryGetValue do sprawdzania, czy w pamięci podręcznej znajduje się czas. Jeśli czas nie jest buforowany, zostanie utworzony nowy wpis i dodany do pamięci podręcznej za pomocą polecenia Set. Klasa CacheKeys jest częścią przykładu pobierania.

public static class CacheKeys
{
    public static string Entry => "_Entry";
    public static string CallbackEntry => "_Callback";
    public static string CallbackMessage => "_CallbackMessage";
    public static string Parent => "_Parent";
    public static string Child => "_Child";
    public static string DependentMessage => "_DependentMessage";
    public static string DependentCTS => "_DependentCTS";
    public static string Ticks => "_Ticks";
    public static string CancelMsg => "_CancelMsg";
    public static string CancelTokenSource => "_CancelTokenSource";
}
public IActionResult CacheTryGetValueSet()
{
    DateTime cacheEntry;

    // Look for cache key.
    if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now;

        // Set cache options.
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            // Keep in cache for this time, reset time if accessed.
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        // Save data in cache.
        _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
    }

    return View("Cache", cacheEntry);
}

Zostanie wyświetlony bieżący czas i czas buforowany:

@model DateTime?

<div>
    <h2>Actions</h2>
    <ul>
        <li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
        <li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAsynchronous">CacheGetOrCreateAsynchronous</a></li>
        <li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAbs">CacheGetOrCreateAbs</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAbsSliding">CacheGetOrCreateAbsSliding</a></li>

    </ul>
</div>

<h3>Current Time: @DateTime.Now.TimeOfDay.ToString()</h3>
<h3>Cached Time: @(Model == null ? "No cached entry found" : Model.Value.TimeOfDay.ToString())</h3>

Poniższy kod używa Set metody rozszerzenia do buforowania danych przez względny czas bez tworzenia MemoryCacheEntryOptions obiektu:

public IActionResult SetCacheRelativeExpiration()
{
    DateTime cacheEntry;

    // Look for cache key.
    if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now;

        // Save data in cache and set the relative expiration time to one day
        _cache.Set(CacheKeys.Entry, cacheEntry, TimeSpan.FromDays(1));
    }

    return View("Cache", cacheEntry);
}

Buforowana DateTime wartość pozostaje w pamięci podręcznej, gdy istnieją żądania w okresie przekroczenia limitu czasu.

Poniższy kod używa instrukcji GetOrCreate i GetOrCreateAsync do buforowania danych.

public IActionResult CacheGetOrCreate()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.SlidingExpiration = TimeSpan.FromSeconds(3);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

public async Task<IActionResult> CacheGetOrCreateAsynchronous()
{
    var cacheEntry = await
        _cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
        {
            entry.SlidingExpiration = TimeSpan.FromSeconds(3);
            return Task.FromResult(DateTime.Now);
        });

    return View("Cache", cacheEntry);
}

Następujące wywołania Get kodu w celu pobrania buforowanego czasu:

public IActionResult CacheGet()
{
    var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry);
    return View("Cache", cacheEntry);
}

Poniższy kod pobiera lub tworzy buforowany element z bezwzględnym wygaśnięciem:

public IActionResult CacheGetOrCreateAbs()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

Buforowany zestaw elementów z tylko przesuwanym wygaśnięciem jest narażony na nigdy nie wygasanie. Jeśli buforowany element jest wielokrotnie uzyskiwany w przedziale czasu wygaśnięcia, element nigdy nie wygaśnie. Połącz przesuwane wygaśnięcie z bezwzględnym wygaśnięciem, aby zagwarantować wygaśnięcie elementu. Bezwzględne wygaśnięcie ustawia górną granicę czasu buforowania elementu, jednocześnie zezwalając na wygaśnięcie elementu wcześniej, jeśli nie jest on żądany w interwale wygasania. Jeśli interwał wygasania przesuwanego lub bezwzględny czas wygaśnięcia, element zostanie wykluczony z pamięci podręcznej.

Poniższy kod pobiera lub tworzy buforowany element z funkcją przesuwania i wygaśnięcia bezwzględnego:

public IActionResult CacheGetOrCreateAbsSliding()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.SetSlidingExpiration(TimeSpan.FromSeconds(3));
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

Powyższy kod gwarantuje, że dane nie będą buforowane dłużej niż bezwzględny czas.

GetOrCreate, GetOrCreateAsynci Get są metodami rozszerzeń w CacheExtensions klasie . Te metody rozszerzają IMemoryCachemożliwości programu .

MemoryCacheEntryOptions

Poniższy przykład:

  • Ustawia przesuwany czas wygaśnięcia. Żądania dostępu do tego buforowanego elementu zresetują przesuwany zegar wygaśnięcia.
  • Ustawia priorytet pamięci podręcznej na CacheItemPriority.NeverRemove.
  • Ustawia element PostEvictionDelegate , który będzie wywoływany po eksmitowanym wpisie z pamięci podręcznej. Wywołanie zwrotne jest uruchamiane w innym wątku niż kod, który usuwa element z pamięci podręcznej.
public IActionResult CreateCallbackEntry()
{
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        // Pin to cache.
        .SetPriority(CacheItemPriority.NeverRemove)
        // Add eviction callback
        .RegisterPostEvictionCallback(callback: EvictionCallback, state: this);

    _cache.Set(CacheKeys.CallbackEntry, DateTime.Now, cacheEntryOptions);

    return RedirectToAction("GetCallbackEntry");
}

public IActionResult GetCallbackEntry()
{
    return View("Callback", new CallbackViewModel
    {
        CachedTime = _cache.Get<DateTime?>(CacheKeys.CallbackEntry),
        Message = _cache.Get<string>(CacheKeys.CallbackMessage)
    });
}

public IActionResult RemoveCallbackEntry()
{
    _cache.Remove(CacheKeys.CallbackEntry);
    return RedirectToAction("GetCallbackEntry");
}

private static void EvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Entry was evicted. Reason: {reason}.";
    ((HomeController)state)._cache.Set(CacheKeys.CallbackMessage, message);
}

Użyj polecenia SetSize, Size i SizeLimit, aby ograniczyć rozmiar pamięci podręcznej

Wystąpienie MemoryCache może opcjonalnie określić i wymusić limit rozmiaru. Limit rozmiaru pamięci podręcznej nie ma zdefiniowanej jednostki miary, ponieważ pamięć podręczna nie ma mechanizmu mierzenia rozmiaru wpisów. Jeśli ustawiono limit rozmiaru pamięci podręcznej, wszystkie wpisy muszą określać rozmiar. Środowisko uruchomieniowe ASP.NET Core nie ogranicza rozmiaru pamięci podręcznej na podstawie ciśnienia pamięci. Deweloper może ograniczyć rozmiar pamięci podręcznej. Określony rozmiar znajduje się w jednostkach wybieranych przez dewelopera.

Na przykład:

  • Jeśli aplikacja internetowa była głównie ciągami buforowania, każdy rozmiar wpisu pamięci podręcznej może być długością ciągu.
  • Aplikacja może określić rozmiar wszystkich wpisów jako 1, a limit rozmiaru to liczba wpisów.

Jeśli SizeLimit nie jest ustawiona, pamięć podręczna rośnie bez ograniczenia. Środowisko uruchomieniowe ASP.NET Core nie przycina pamięci podręcznej, gdy pamięć systemowa jest niska. Aplikacje muszą być zaprojektowane tak, aby:

  • Ogranicz wzrost pamięci podręcznej.
  • Wywołanie Compact lub Remove gdy dostępna pamięć jest ograniczona:

Poniższy kod tworzy bezjednostki stały rozmiar MemoryCache dostępny przez iniekcję zależności:

// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache 
{
    public MemoryCache Cache { get; private set; }
    public MyMemoryCache()
    {
        Cache = new MemoryCache(new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
    }
}

SizeLimit nie ma jednostek. Buforowane wpisy muszą określać rozmiar niezależnie od jednostek, które uznają za najbardziej odpowiednie, jeśli został ustawiony limit rozmiaru pamięci podręcznej. Wszyscy użytkownicy wystąpienia pamięci podręcznej powinni używać tego samego systemu jednostkowego. Wpis nie będzie buforowany, jeśli suma buforowanych rozmiarów wpisu przekracza wartość określoną przez SizeLimit. Jeśli nie ustawiono limitu rozmiaru pamięci podręcznej, rozmiar pamięci podręcznej ustawiony na wpis zostanie zignorowany.

Poniższy kod rejestruje się MyMemoryCache w kontenerze wstrzykiwania zależności.

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddSingleton<MyMemoryCache>();
}

MyMemoryCache Jest tworzony jako niezależna pamięć podręczna dla składników, które mają świadomość tego rozmiaru ograniczonej pamięci podręcznej i wiedzą, jak odpowiednio ustawić rozmiar wpisu pamięci podręcznej.

Poniższy kod używa metody MyMemoryCache:

public class SetSize : PageModel
{
    private MemoryCache _cache;
    public static readonly string MyKey = "_MyKey";

    public SetSize(MyMemoryCache memoryCache)
    {
        _cache = memoryCache.Cache;
    }

    [TempData]
    public string DateTime_Now { get; set; }

    public IActionResult OnGet()
    {
        if (!_cache.TryGetValue(MyKey, out string cacheEntry))
        {
            // Key not in cache, so get data.
            cacheEntry = DateTime.Now.TimeOfDay.ToString();

            var cacheEntryOptions = new MemoryCacheEntryOptions()
                // Set cache entry size by extension method.
                .SetSize(1)
                // Keep in cache for this time, reset time if accessed.
                .SetSlidingExpiration(TimeSpan.FromSeconds(3));

            // Set cache entry size via property.
            // cacheEntryOptions.Size = 1;

            // Save data in cache.
            _cache.Set(MyKey, cacheEntry, cacheEntryOptions);
        }

        DateTime_Now = cacheEntry;

        return RedirectToPage("./Index");
    }
}

Rozmiar wpisu pamięci podręcznej można ustawić za pomocą Size metod rozszerzeń lub SetSize :

public IActionResult OnGet()
{
    if (!_cache.TryGetValue(MyKey, out string cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now.TimeOfDay.ToString();

        var cacheEntryOptions = new MemoryCacheEntryOptions()
            // Set cache entry size by extension method.
            .SetSize(1)
            // Keep in cache for this time, reset time if accessed.
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        // Set cache entry size via property.
        // cacheEntryOptions.Size = 1;

        // Save data in cache.
        _cache.Set(MyKey, cacheEntry, cacheEntryOptions);
    }

    DateTime_Now = cacheEntry;

    return RedirectToPage("./Index");
}

MemoryCache.Compact

MemoryCache.Compact próbuje usunąć określony procent pamięci podręcznej w następującej kolejności:

  • Wszystkie wygasłe elementy.
  • Elementy według priorytetu. Elementy o najniższym priorytecie są najpierw usuwane.
  • Najmniej ostatnio używane obiekty.
  • Elementy z najwcześniejszym bezwzględnym wygaśnięciem.
  • Elementy z najwcześniejszym przesuwanym wygaśnięciem.

Przypięte elementy o priorytecie NeverRemove nigdy nie są usuwane. Poniższy kod usuwa element pamięci podręcznej i wywołuje metodę Compact:

_cache.Remove(MyKey);

// Remove 33% of cached items.
_cache.Compact(.33);   
cache_size = _cache.Count;

Aby uzyskać więcej informacji, zobacz Compact source on GitHub (Kompaktowanie źródła w usłudze GitHub).

Zależności pamięci podręcznej

W poniższym przykładzie pokazano, jak wygasnąć wpis pamięci podręcznej, jeśli wpis zależny wygaśnie. Element jest CancellationChangeToken dodawany do buforowanego elementu. Gdy Cancel jest wywoływana w obiekcie CancellationTokenSource, oba wpisy pamięci podręcznej są eksmitowane.

public IActionResult CreateDependentEntries()
{
    var cts = new CancellationTokenSource();
    _cache.Set(CacheKeys.DependentCTS, cts);

    using (var entry = _cache.CreateEntry(CacheKeys.Parent))
    {
        // expire this entry if the dependant entry expires.
        entry.Value = DateTime.Now;
        entry.RegisterPostEvictionCallback(DependentEvictionCallback, this);

        _cache.Set(CacheKeys.Child,
            DateTime.Now,
            new CancellationChangeToken(cts.Token));
    }

    return RedirectToAction("GetDependentEntries");
}

public IActionResult GetDependentEntries()
{
    return View("Dependent", new DependentViewModel
    {
        ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent),
        ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child),
        Message = _cache.Get<string>(CacheKeys.DependentMessage)
    });
}

public IActionResult RemoveChildEntry()
{
    _cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel();
    return RedirectToAction("GetDependentEntries");
}

private static void DependentEvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Parent entry was evicted. Reason: {reason}.";
    ((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message);
}

Użycie elementu CancellationTokenSource umożliwia eksmitowanie wielu wpisów pamięci podręcznej jako grupy. using We wzorcu w powyższym kodzie wpisy pamięci podręcznej utworzone wewnątrz using bloku będą dziedziczyć wyzwalacze i ustawienia wygasania.

Dodatkowe uwagi

  • Wygaśnięcie nie jest wykonywane w tle. Nie ma czasomierza, który aktywnie skanuje pamięć podręczną pod kątem wygasłych elementów. Każde działanie w pamięci podręcznej (Get, Set, Remove) może wyzwolić skanowanie w tle pod kątem wygasłych elementów. Czasomierz w elemecie CancellationTokenSource (CancelAfter) usuwa również wpis i wyzwala skanowanie pod kątem wygasłych elementów. W poniższym przykładzie użyto CancellationTokenSource(TimeSpan) dla zarejestrowanego tokenu. Gdy ten token zostanie wyzwolony, natychmiast usunie wpis i uruchomi wywołania zwrotne eksmisji:

    public IActionResult CacheAutoExpiringTryGetValueSet()
    {
        DateTime cacheEntry;
    
        if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
        {
            cacheEntry = DateTime.Now;
    
            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
    
            var cacheEntryOptions = new MemoryCacheEntryOptions()
                .AddExpirationToken(new CancellationChangeToken(cts.Token));
    
            _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
        }
    
        return View("Cache", cacheEntry);
    }
    
  • W przypadku ponownego wypełniania elementu pamięci podręcznej przy użyciu wywołania zwrotnego:

    • Wiele żądań może znaleźć wartość klucza buforowanego pustą, ponieważ wywołanie zwrotne nie zostało ukończone.
    • Może to spowodować ponowne wypełnienie buforowanego elementu kilkoma wątkami.
  • Gdy jeden wpis pamięci podręcznej jest używany do utworzenia innego, element podrzędny kopiuje tokeny wygasania wpisu nadrzędnego i ustawienia wygasania na podstawie czasu. Element podrzędny nie wygasł przez ręczne usunięcie ani zaktualizowanie wpisu nadrzędnego.

  • Służy PostEvictionCallbacks do ustawiania wywołań zwrotnych, które zostaną wyzwolone po eksmitowaniu wpisu pamięci podręcznej z pamięci podręcznej. W przykładowym kodzie CancellationTokenSource.Dispose() jest wywoływany w celu zwolnienia niezarządzanych zasobów używanych przez program CancellationTokenSource. Jednak obiekt CancellationTokenSource nie jest usuwany natychmiast, ponieważ jest on nadal używany przez wpis pamięci podręcznej. Element CancellationToken jest przekazywany do, aby MemoryCacheEntryOptions utworzyć wpis pamięci podręcznej, który wygasa po upływie określonego czasu. Dlatego Dispose nie należy wywoływać, dopóki wpis pamięci podręcznej nie zostanie usunięty lub wygasł. Przykładowy kod wywołuje metodę RegisterPostEvictionCallback w celu zarejestrowania wywołania zwrotnego, które będzie wywoływane po eksmitowanym wpisie pamięci podręcznej i usuwa CancellationTokenSource element w tym wywołaniu zwrotnym.

  • W przypadku większości aplikacji IMemoryCache jest włączona. Na przykład wywoływanie AddMvcmetod , , AddControllersWithViews, AddRazorPagesAddMvcCore().AddRazorViewEnginei wielu innych Add{Service} metod w systemie ConfigureServicesumożliwia .IMemoryCache W przypadku aplikacji, które nie wywołuje jednej z powyższych Add{Service} metod, może być konieczne wywołanie AddMemoryCache metody w pliku ConfigureServices.

Aktualizacja pamięci podręcznej w tle

Użyj usługi w tle, takiej jak IHostedService , aby zaktualizować pamięć podręczną. Usługa w tle może ponownie skompilować wpisy, a następnie przypisać je do pamięci podręcznej tylko wtedy, gdy są gotowe.

Dodatkowe zasoby