Cache in memoria in ASP.NET Core

Di Rick Anderson, John Luo e Steve Smith

La memorizzazione nella cache può migliorare significativamente le prestazioni e la scalabilità di un'app riducendo il lavoro necessario per generare contenuti. La memorizzazione nella cache funziona meglio con i dati che cambiano raramente ed è costosa da generare. La memorizzazione nella cache crea una copia dei dati che può essere restituita molto più velocemente rispetto all'origine. Le app devono essere scritte e testate in modo da non dipendere mai dai dati memorizzati nella cache.

ASP.NET Core supporta diverse cache. La cache più semplice si basa sull'interfaccia IMemoryCache . IMemoryCache Rappresenta una cache memorizzata nella memoria del server web. Le app in esecuzione in una server farm (più server) devono garantire che le sessioni siano permanenti quando si usa la cache in memoria. Le sessioni permanenti assicurano che le richieste provenienti da un client vengano inviate tutte allo stesso server. Ad esempio, un'app Web Azure usa Microsoft Application Request Routing (ARR) per instradare tutte le richieste allo stesso server.

Le sessioni non di tipostick in una Web farm richiedono una cache distribuita per evitare problemi di coerenza della cache. Per alcune app, una cache distribuita può supportare una scalabilità orizzontale più elevata rispetto a una cache in memoria. L'utilizzo di una cache distribuita trasferisce la memoria cache a un processo esterno.

La cache in memoria può memorizzare qualsiasi oggetto. L'interfaccia della cache distribuita è limitata a byte[]. La cache in memoria e quella distribuita archiviano gli elementi della cache come coppie chiave-valore.

Usare System.Runtime.Caching/MemoryCache

System.Runtime.Caching / MemoryCache (pacchetto NuGet) può essere usato con:

  • .NET Standard 2.0 o versione successiva
  • Qualsiasi implementazione .NET destinata a .NET Standard 2.0 o versione successiva (ad esempio ASP.NET Core 3.1 o versione successiva)
  • .NET Framework 4.5 o versione successiva

Microsoft. Extensions.Caching.Memory/IMemoryCache (descritto in questo articolo) è consigliato su System.Runtime.Caching/MemoryCache perché offre una migliore integrazione con ASP.NET Core. Ad esempio, IMemoryCache funziona in modo nativo con ASP.NET Core dependency injection.

Da utilizzare System.Runtime.Caching/MemoryCache come bridge di compatibilità durante la conversione del codice da ASP.NET 4.x a ASP.NET Core.

Esaminare le linee guida per la memorizzazione nella cache in memoria

Le linee guida seguenti si applicano alla memorizzazione nella cache in memoria:

  • Il codice deve avere sempre un'opzione di fallback per recuperare i dati e non dipendere dalla disponibilità di un valore memorizzato nella cache.

  • La cache usa memoria, che è una risorsa scarsa. Limitare la crescita della cache:

    • Non inserire input esterno nella cache. Ad esempio, l'uso di input arbitrario fornito dall'utente come chiave della cache non è consigliato perché l'input potrebbe utilizzare una quantità imprevedibile di memoria.

    • Utilizzare le scadenze per limitare la crescita della cache.

    • Utilizzare SetSize, Size e SizeLimit per limitare le dimensioni della cache. Il runtime ASP.NET Core non limita le dimensioni della cache in base alla pressione della memoria. Lo sviluppatore è responsabile della limitazione delle dimensioni della cache.

Creare un'istanza di IMemoryCache

La memorizzazione nella cache in memoria è un servizio a cui fa riferimento un'app usando l'iniezione delle dipendenze.

Warning

Se la stessa cache viene usata da più framework o librerie, si tratta di una cache condivisa . Se si usa una cache di memoria condivisa dall'inserimento delle dipendenze e si usa anche SetSize, Size e SizeLimit per limitare le dimensioni della cache, l'app potrebbe fallire.

Quando viene impostato un limite di dimensioni in una cache, tutte le voci devono specificare una dimensione quando vengono aggiunte. Questo approccio può causare problemi perché gli sviluppatori potrebbero non avere il controllo completo su ciò che usa la cache condivisa.

Per limitare le dimensioni della cache con il metodo SetSize, la proprietà Size o la proprietà SizeLimit, creare un singleton della cache per la memorizzazione nella cache. Per ulteriori informazioni e un esempio, vedere Utilizzare SetSize, Size e SizeLimit per limitare le dimensioni della cache.

Richiedi l'istanza IMemoryCache nel costruttore:

public class IndexModel : PageModel
{
    private readonly IMemoryCache _memoryCache;

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

    // ...

Il codice seguente usa il TryGetValue metodo per verificare se un'ora si trova nella cache. Se un tempo non è memorizzato nella cache, viene creata una nuova voce e aggiunta alla cache utilizzando il metodo 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;
}

Nel codice precedente, l'entry della cache viene configurata con una scadenza scorrevole di 3 secondi. Se la voce della cache non è accessibile per più di 3 secondi, la voce viene rimossa dalla cache. Ogni volta che si accede alla voce della cache, questa rimane nella cache per altri 3 secondi. La CacheKeys classe fa parte dell'esempio di download.

Vengono visualizzate l'ora corrente e l'ora memorizzata nella cache:

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

Il codice seguente utilizza il metodo di Set estensione per memorizzare nella cache i dati per un periodo di tempo relativo senza MemoryCacheEntryOptions:

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

Nel codice precedente, la voce della cache è configurata con una scadenza relativa di un giorno. La voce della cache viene eliminata dalla cache dopo un giorno, anche se si accede alla voce durante il periodo di timeout.

Il codice seguente usa i metodi GetOrCreate e GetOrCreateAsync per memorizzare i dati nella cache.

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

    // ...
}

Il codice seguente chiama il Get metodo per recuperare l'ora memorizzata nella cache:

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

Il codice seguente ottiene o crea un elemento memorizzato nella cache con scadenza assoluta:

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

Un set di elementi memorizzato nella cache con solo una scadenza variabile rischia di non scadere mai. Se l'elemento memorizzato nella cache viene eseguito ripetutamente entro l'intervallo di scadenza variabile, l'elemento non scade mai. La combinazione di una scadenza scorrevole con una scadenza assoluta garantisce la scadenza dell'elemento. La scadenza assoluta imposta un limite superiore per quanto tempo l'elemento può essere memorizzato nella cache. Consente comunque la scadenza dell'elemento in precedenza, se l'elemento non viene richiesto entro l'intervallo di scadenza scorrevole. Se l'intervallo di scadenza variabile o il tempo di scadenza assoluto passa, l'elemento viene rimosso dalla cache.

Il codice seguente ottiene o crea un elemento memorizzato nella cache con scadenza variabile e assoluta:

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

Il codice precedente garantisce che i dati non siano memorizzati nella cache più a lungo del tempo assoluto.

metodi di estensione GetOrCreate, GetOrCreateAsync, e Get nella classe CacheExtensions. Questi metodi estendono la capacità di IMemoryCache.

Creare MemoryCacheEntryOptions per un elemento

Nell'esempio seguente viene mostrato come creare MemoryCacheEntryOptions per un'entrata. Il codice completa le attività seguenti:

  • Imposta la priorità della cache su CacheItemPriority.NeverRemove.

  • Imposta un PostEvictionDelegate oggetto da chiamare dopo la rimozione della voce dalla cache. Il callback viene eseguito su un thread diverso dal codice che rimuove l'elemento dalla cache.

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

Limitare le dimensioni della cache con SetSize, Size e SizeLimit

Un'istanza MemoryCache può facoltativamente specificare e applicare un limite di dimensioni. Il limite delle dimensioni della cache non ha un'unità di misura definita perché la cache non dispone di un meccanismo per misurare le dimensioni delle voci. Se il limite delle dimensioni della cache è impostato, tutte le voci devono specificare le dimensioni. Il runtime ASP.NET Core non limita le dimensioni della cache in base all'utilizzo eccessivo della memoria. Spetta allo sviluppatore limitare la dimensione della cache. La dimensione specificata è espressa in unità scelte dallo sviluppatore.

Per esempio:

  • Se l'app Web memorizza nella cache principalmente le stringhe, ogni dimensione della voce della cache potrebbe essere la lunghezza della stringa.
  • L'app può specificare la dimensione di ciascuna voce pari a 1 e il limite di grandezza è determinato dal numero di voci.

Se la SizeLimit proprietà non è impostata, la cache aumenta senza limiti. Il runtime ASP.NET Core non taglia la cache quando la memoria di sistema è insufficiente. Le app devono essere progettate per:

  • Limitare la crescita della cache.
  • Chiamare il Compact metodo o Remove quando la memoria disponibile è limitata.

Il codice seguente crea un'istanza a dimensione MemoryCache fissa senza unità di misura accessibile tramite iniezione delle dipendenze:

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

La SizeLimit proprietà non dispone di unità. Le voci memorizzate nella cache devono specificare le dimensioni in qualsiasi unità che considerino più appropriate quando viene impostato il limite di dimensioni della cache. Tutti gli utenti di un'istanza della cache devono utilizzare lo stesso sistema di unità. Una voce non viene memorizzata nella cache se la somma delle dimensioni delle voci memorizzate nella cache supera il valore specificato da SizeLimit. Se non è impostato alcun limite per la dimensione della cache, la dimensione della cache impostata per la voce viene ignorata.

Il codice seguente registra l'istanza MyMemoryCache nel contenitore di iniezione delle dipendenze :

var builder = WebApplication.CreateBuilder(args);

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

MyMemoryCache è creata come cache di memoria indipendente per i componenti che sono a conoscenza di questa cache con dimensioni limitate e sanno impostare appropriatamente le dimensioni delle voci della cache.

Le dimensioni della voce della cache possono essere impostate usando il SetSize metodo di estensione o la Size proprietà :

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

Nel codice precedente, le due righe evidenziate ottengono lo stesso risultato dell'impostazione delle dimensioni della voce della cache. Il SetSize metodo viene fornito per praticità durante il concatenamento delle chiamate a new MemoryCacheOptions().

Rimuovere gli elementi della cache con MemoryCache.Compact

Il MemoryCache.Compact metodo tenta di rimuovere la percentuale specificata della cache nell'ordine seguente:

  • Tutti gli elementi scaduti
  • Elementi per priorità, in cui gli elementi con priorità più bassa vengono rimossi per primi
  • Oggetti usati meno di recente
  • Elementi con la scadenza assoluta più vicina
  • Elementi con la scadenza scorrevole più precoce

Gli elementi bloccati con priorità NeverRemove non vengono mai rimossi. Il codice seguente rimuove un elemento della cache e chiama il metodo Compact per rimuovere il 25% delle voci archiviate nella cache.

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

Per ulteriori informazioni, vedere la sorgente compatta su GitHub.

Rimuovere una voce della cache con dipendenze scadute

Nell'esempio seguente viene illustrato come far scadere una voce della cache se scade una voce dipendente. A CancellationChangeToken viene aggiunto all'elemento memorizzato nella cache. Quando il metodo Cancel viene chiamato sull'oggetto CancellationTokenSource, entrambe le voci della cache vengono rimosse:

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

Quando si utilizza l'oggetto CancellationTokenSource, è possibile rimuovere più voci della cache come gruppo. Con il using modello nel codice precedente, le voci della cache create all'interno dell'ambito using ereditano i trigger e le impostazioni di scadenza.

Rivedere le note sulla memorizzazione nella cache in memoria

Le note seguenti si applicano alla memorizzazione nella cache in memoria:

  • La scadenza non avviene in background.

    Non esiste un timer che esegue attivamente la scansione della cache alla ricerca di elementi scaduti. Qualsiasi attività nella cache (tramite Get, TryGetValue, Seto Remove) può attivare un'analisi in background per gli elementi scaduti. Un timer impostato sull'oggetto CancellationTokenSource (utilizzando il CancelAfter metodo ) rimuove anche la voce e attiva un'analisi degli elementi scaduti.

    Nell'esempio seguente viene usato il CancellationTokenSource(TimeSpan) costruttore di overload per il token registrato. Quando questo token viene attivato, rimuove immediatamente la voce e attiva i callback di espulsione.

    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);
    }
    
  • Quando si usa un callback per ripopolare un elemento della cache:

    • È possibile che più richieste rilevino che il valore della chiave memorizzata nella cache è vuoto perché il callback non è terminato.
    • Questo approccio può comportare il ripopolamento di più thread dell'elemento memorizzato nella cache.
  • Quando una voce della cache (padre) crea un'altra voce (figlio), il figlio copia i token di scadenza della voce padre e le impostazioni di scadenza basate sul tempo. L'elemento figlio non scade a causa della rimozione manuale o dell'aggiornamento dell'elemento padre.

  • Utilizzare la PostEvictionCallbacks proprietà per specificare quali callback devono essere attivati dopo la rimozione di una voce della cache dalla cache.

  • Per la maggior parte delle app, IMemoryCache è abilitato. Ad esempio, chiamare AddMvc, AddControllersWithViews, AddRazorPages, AddMvcCore().AddRazorViewEngine e molti altri metodi Add{Service} nel file Program.cs abilita IMemoryCache.

    Per le app che non chiamano uno dei metodi indicati Add{Service} , potrebbe essere necessario chiamare il AddMemoryCache metodo nel file Program.cs .

Usare un aggiornamento della cache in background

Usare un servizio in background , ad esempio l'interfaccia IHostedService per aggiornare la cache. Il servizio in background può ricompilare le voci e assegnarle alla cache solo dopo che sono pronte.

Visualizzare o scaricare il codice di esempio (come scaricare)

Nozioni di base sulla memorizzazione nella cache

La memorizzazione nella cache può migliorare significativamente le prestazioni e la scalabilità di un'app riducendo il lavoro necessario per generare contenuti. La memorizzazione nella cache funziona meglio con i dati che cambiano raramente ed è costosa da generare. La memorizzazione nella cache crea una copia dei dati che può essere restituita molto più velocemente rispetto all'origine. Le app devono essere scritte e testate in modo da non dipendere mai dai dati memorizzati nella cache.

ASP.NET Core supporta diverse cache. La cache più semplice si basa su IMemoryCache. IMemoryCache Rappresenta una cache memorizzata nella memoria del server web. Le app in esecuzione in una server farm (più server) devono garantire che le sessioni siano permanenti quando si usa la cache in memoria. Le sessioni permanenti assicurano che le richieste successive da un client vengano inviate tutte allo stesso server. Ad esempio, le app Web di Azure usano Application Request Routing (ARR) per instradare tutte le richieste successive allo stesso server.

Le sessioni non permanenti in una Web farm richiedono una cache distribuita per evitare problemi di coerenza della cache. Per alcune app, una cache distribuita può supportare una scalabilità orizzontale più elevata rispetto a una cache in memoria. L'utilizzo di una cache distribuita trasferisce la memoria cache a un processo esterno.

La cache in memoria può memorizzare qualsiasi oggetto. L'interfaccia della cache distribuita è limitata a byte[]. La cache in memoria e quella distribuita archiviano gli elementi della cache come coppie chiave-valore.

System.Runtime.Caching/MemoryCache

System.Runtime.Caching / MemoryCache (pacchetto NuGet) può essere usato con:

  • .NET Standard 2.0 o versione successiva.
  • Qualsiasi implementazione .NET destinata a .NET Standard 2.0 o versione successiva. Ad esempio, ASP.NET Core 3.1 o versioni successive.
  • .NET Framework 4.5 o versione successiva.

Microsoft.Extensions.Caching.Memory/IMemoryCache (descritto in questo articolo) è consigliato rispetto a System.Runtime.Caching/MemoryCache perché è meglio integrato in ASP.NET Core. Ad esempio, IMemoryCache funziona in modo nativo con ASP.NET Core dependency injection.

Da utilizzare System.Runtime.Caching/MemoryCache come bridge di compatibilità durante la conversione del codice da ASP.NET 4.x a ASP.NET Core.

Linee guida per la cache

  • Il codice deve sempre avere un'opzione di fallback per recuperare i dati e non dipendere dalla disponibilità di un valore memorizzato nella cache.
  • La cache utilizza una risorsa scarsa, la memoria. Limita la crescita della cache:

Usare IMemoryCache

Warning

L'uso di una cache di memoria condivisa da Dependency Injection e la chiamata a SetSize, Size o SizeLimit per limitare le dimensioni della cache può causare il malfunzionamento dell'app. Quando viene impostato un limite di dimensione per una cache, tutte le voci devono specificare una dimensione al momento dell'aggiunta. Ciò può causare problemi poiché gli sviluppatori potrebbero non avere il controllo completo su ciò che utilizza la cache condivisa. Quando si utilizza SetSize, Size, o SizeLimit per limitare la cache, creare un singleton di cache per la memorizzazione nella cache. Per ulteriori informazioni e un esempio, vedere Utilizzare SetSize, Size e SizeLimit per limitare le dimensioni della cache. Una cache condivisa è una cache condivisa da altri framework o librerie.

La memorizzazione nella cache in memoria è un servizio a cui si fa riferimento in un'app che utilizza l'inserimento delle dipendenze. Richiedi l'istanza IMemoryCache nel costruttore:

public class HomeController : Controller
{
    private IMemoryCache _cache;

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

Il codice seguente viene utilizzato TryGetValue per verificare se nella cache è presente un'ora. Se un tempo non è memorizzato nella cache, viene creata una nuova voce e aggiunta alla cache con Set. La CacheKeys classe fa parte dell'esempio di download.

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

Vengono visualizzate l'ora corrente e l'ora memorizzata nella cache:

@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>

Nel codice seguente viene utilizzato il metodo di estensione per memorizzare nella cache i Set dati per un periodo di tempo relativo senza creare l'oggetto MemoryCacheEntryOptions :

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

Il valore memorizzato DateTime nella cache rimane nella cache finché sono presenti richieste entro il periodo di timeout.

Nel codice seguente vengono utilizzati GetOrCreate e GetOrCreateAsync per memorizzare i dati nella cache.

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

Il codice seguente chiama Get per recuperare l'ora memorizzata nella cache:

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

Il codice seguente ottiene o crea un elemento memorizzato nella cache con scadenza assoluta:

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

    return View("Cache", cacheEntry);
}

Un set di elementi memorizzato nella cache con solo una scadenza variabile rischia di non scadere mai. Se l'elemento memorizzato nella cache viene eseguito ripetutamente entro l'intervallo di scadenza variabile, l'elemento non scade mai. Combina una scadenza variabile con una scadenza assoluta per garantire la scadenza dell'articolo. La scadenza assoluta imposta un limite superiore per il tempo in cui l'elemento può essere memorizzato nella cache, consentendo comunque la scadenza anticipata dell'elemento se non viene richiesto entro l'intervallo di scadenza variabile. Se l'intervallo di scadenza scorrevole o l'ora di scadenza assoluta sono trascorsi, l'elemento viene eliminato dalla cache.

Il codice seguente ottiene o crea un elemento memorizzato nella cache con scadenza variabile e assoluta:

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

Il codice precedente garantisce che i dati non vengano memorizzati nella cache più a lungo del tempo assoluto.

metodi di estensione GetOrCreate, GetOrCreateAsync, e Get nella classe CacheExtensions. Questi metodi estendono la capacità di IMemoryCache.

MemoryCacheEntryOptions

L'esempio seguente:

  • Imposta un'ora di scadenza variabile. Le richieste che accedono a questo elemento nella cache reimposteranno il termine di scadenza scorrevole.
  • Imposta la priorità della cache su CacheItemPriority.NeverRemove.
  • Imposta un PostEvictionDelegate che verrà chiamato dopo che la voce è stata rimossa dalla cache. Il callback viene eseguito su un thread diverso dal codice che rimuove l'elemento dalla cache.
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);
}

Utilizzare SetSize, Size e SizeLimit per limitare le dimensioni della cache

Un'istanza MemoryCache può facoltativamente specificare e applicare un limite di dimensioni. Il limite delle dimensioni della cache non dispone di un'unità di misura definita perché la cache non dispone di un meccanismo per misurare le dimensioni delle voci. Se il limite delle dimensioni della cache è impostato, tutte le voci devono specificare le dimensioni. Il runtime ASP.NET Core non limita le dimensioni della cache in base all'utilizzo eccessivo della memoria. Spetta allo sviluppatore limitare la dimensione della cache. La dimensione specificata è espressa in unità scelte dallo sviluppatore.

Per esempio:

  • Se l'app Web memorizzasse principalmente le stringhe nella cache, la dimensione di ciascuna voce nella cache potrebbe essere la lunghezza della stringa.
  • L'app può specificare la dimensione di tutte le voci a 1, e il limite di dimensione è il conteggio delle voci.

Se SizeLimit non è impostato, la cache aumenta senza limiti. Il runtime ASP.NET Core non taglia la cache quando la memoria di sistema è insufficiente. Le app devono essere progettate per:

  • Limitare la crescita della cache.
  • Chiamata Compact o Remove quando la memoria disponibile è limitata:

Il codice seguente crea una dimensione MemoryCache fissa senza unità accessibile tramite inserimento delle dipendenze:

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

SizeLimit non dispone di unità. Le voci memorizzate nella cache devono specificare la dimensione in qualsiasi unità che ritengono più appropriata se è stato impostato il limite della dimensione della cache. Tutti gli utenti di un'istanza della cache devono utilizzare lo stesso sistema di unità. Una voce non verrà memorizzata nella cache se la somma delle dimensioni delle voci memorizzate nella cache supera il valore specificato da SizeLimit. Se non è impostato alcun limite per la dimensione della cache, la dimensione della cache impostata per la voce verrà ignorata.

Il codice seguente registra MyMemoryCache con il contenitore di inserimento delle dipendenze.

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

MyMemoryCache viene creata come memoria cache indipendente per i componenti che conoscono questa cache a dimensione limitata e sanno come impostare correttamente la dimensione delle voci della cache.

Il codice seguente usa 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");
    }
}

La dimensione della voce della cache può essere impostata da Size o dai metodi di espansione 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 tenta di rimuovere la percentuale specificata della cache nell'ordine seguente:

  • Tutti gli elementi scaduti.
  • Elementi per priorità. Gli elementi con priorità più bassa vengono rimossi per primi.
  • Oggetti utilizzati meno di recente.
  • Elementi con la prima scadenza assoluta.
  • Elementi con la prima scadenza variabile.

Gli elementi bloccati con priorità NeverRemove non vengono mai rimossi. Il codice seguente rimuove un elemento della cache e chiama Compact:

_cache.Remove(MyKey);

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

Per ulteriori informazioni, vedere la sorgente compatta su GitHub.

Dipendenze della cache

Nell'esempio seguente viene illustrato come far scadere una voce della cache se scade una voce dipendente. A CancellationChangeToken viene aggiunto all'elemento memorizzato nella cache. Quando Cancel viene chiamato su CancellationTokenSource, entrambe le voci della cache CancellationTokenSource vengono rimosse.

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

L'utilizzo di a CancellationTokenSource consente di rimuovere più voci della cache come gruppo. Con il using modello nel codice precedente, le entrate della cache create all'interno del blocco erediteranno i trigger e le impostazioni di scadenza.

Note aggiuntive

  • La scadenza non avviene in background. Non esiste un timer che scansiona attivamente la cache alla ricerca di elementi scaduti. Qualsiasi attività sulla cache (Get, Set, Remove) può attivare un'analisi in background per gli elementi scaduti. Un timer sul CancellationTokenSource (CancelAfter) rimuove la voce e attiva anche una scansione per gli elementi scaduti. Nell'esempio seguente, il token registrato CancellationTokenSource(TimeSpan) viene utilizzato. Quando questo token viene attivato, rimuove immediatamente la voce e attiva i callback di sfratto.

    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);
    }
    
  • Quando si utilizza una richiamata per ripopolare un elemento della cache:

    • Più richieste possono trovare il valore della chiave memorizzato nella cache vuoto perché il callback non è stato completato.
    • Ciò può comportare il ripopolamento dell'elemento memorizzato nella cache da parte di diversi thread.
  • Quando una voce della cache viene utilizzata per crearne un'altra, l'elemento figlio copia i token di scadenza della voce padre e le impostazioni di scadenza basate sul tempo. L'elemento figlio non è stato rimosso a causa della rimozione manuale o dell'aggiornamento della voce principale.

  • Usa PostEvictionCallbacks per impostare i callback che verranno attivati dopo che la voce della cache è stata scartata dalla cache. Nel codice di esempio, CancellationTokenSource.Dispose() viene chiamato per rilasciare le risorse non gestite utilizzate da CancellationTokenSource. Tuttavia, il CancellationTokenSource non viene eliminato immediatamente perché è ancora utilizzato dalla voce della cache. Il CancellationToken viene passato a MemoryCacheEntryOptions per creare un elemento della cache che scade dopo un certo tempo. Quindi Dispose non deve essere chiamato fino a quando l'elemento della cache non viene rimosso o non è scaduto. Il codice di esempio chiama il metodo RegisterPostEvictionCallback per registrare un callback che verrà richiamato quando la voce della cache viene rimossa e, in quel callback, elimina il CancellationTokenSource.

  • Per la maggior parte delle app, IMemoryCache è abilitato. Ad esempio, chiamando AddMvc, AddControllersWithViews, AddRazorPages, AddMvcCore().AddRazorViewEngine e molti altri metodi Add{Service} in ConfigureServices, si abilita IMemoryCache. Per le app che non chiamano uno dei metodi precedenti Add{Service}, potrebbe essere necessario chiamare AddMemoryCache in ConfigureServices.

Aggiornamento della cache in background

Utilizzare un servizio in background , ad esempio IHostedService per aggiornare la cache. Il servizio in background può ricalcolare le voci dei dati e assegnarle successivamente alla cache solo quando sono pronte.

Risorse aggiuntive