Condividi tramite


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 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 provenienti 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 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 perché System.Runtime.Caching/MemoryCache è 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:
    • Non inserire input esterno nella cache. Ad esempio, l'utilizzo di input arbitrario fornito dall'utente come chiave della cache non è consigliato poiché 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 all'utilizzo eccessivo della memoria. Spetta allo sviluppatore limitare la dimensione della cache.

Usare IMemoryCache

Warning

L'uso di una cache di memoria condivisa da Dependency Injection e la chiamata SetSizea , Size, o SizeLimit per limitare le dimensioni della cache può causare l'errore 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 viene fatto riferimento da un'app che usa l'inserimento delle dipendenze. Richiedi l'istanza IMemoryCache nel costruttore:

public class IndexModel : PageModel
{
    private readonly IMemoryCache _memoryCache;

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

    // ...

Il codice seguente viene utilizzato TryGetValue per verificare se nella cache è presente un'ora. Se un'ora non è memorizzata nella cache, viene creata una nuova voce che viene aggiunta alla cache con 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, la voce della cache è configurata con una scadenza variabile di tre secondi. Se non si accede alla voce della cache per più di tre secondi, 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 rimossa dalla cache dopo un giorno, anche se vi si accede entro questo periodo di timeout.

Nel codice seguente vengono utilizzati 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 Get 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. 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 variabile o l'ora di scadenza assoluta trascorrono trascorsi, 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 vengano memorizzati nella cache più a lungo del tempo assoluto.

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

MemoryCacheEntryOptions

L'esempio seguente:

  • Imposta la priorità della cache su CacheItemPriority.NeverRemove.
  • Imposta un PostEvictionDelegate che viene 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 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}.");
}

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 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 principalmente nella cache le stringhe, ogni dimensione della voce della cache potrebbe essere la lunghezza della stringa.
  • L'app può specificare la dimensione di tutte le voci come 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.
  • Chiamare Compact o Remove quando la memoria disponibile è limitata.

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

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

SizeLimit non ha unità. Le voci memorizzate nella cache devono specificare la dimensione in qualsiasi unità che ritengano 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 viene ignorata.

Il codice seguente viene MyMemoryCache registrato con il contenitore di inserimento delle dipendenze :

var builder = WebApplication.CreateBuilder(args);

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

MyMemoryCache viene creato come cache di memoria indipendente per i componenti che sono consapevoli di questa cache di dimensioni limitate e sanno come impostare la dimensione della voce della cache in modo appropriato.

La dimensione della voce della cache può essere impostata utilizzando il metodo di SetSize 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. SetSize viene fornito per comodità quando si concatenano le chiamate su new MemoryCacheOptions().

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 per rimuovere 25% di voci memorizzate nella cache:

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

Per altre informazioni, vedere l'origine compatta in 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 sul , entrambe le voci della CancellationTokenSourcecache 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();
}

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

Note aggiuntive

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

    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 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 secondario non è scaduto a causa della rimozione manuale o dell'aggiornamento della voce principale.

  • Consente PostEvictionCallbacks di impostare i callback che verranno attivati dopo che la voce della cache è stata rimossa dalla cache.

  • Per la maggior parte delle app, IMemoryCache è abilitato. Ad esempio, la chiamata a , , , , e a molti altri AddMvc metodi in AddControllersWithViews, abilita AddRazorPages. AddMvcCore().AddRazorViewEngineAdd{Service}Program.csIMemoryCache Per le app che non chiamano uno dei metodi precedenti Add{Service} , potrebbe essere necessario chiamare AddMemoryCacheProgram.cs.

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 e quindi assegnarle alla cache solo quando sono pronte.

Risorse aggiuntive

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 perché System.Runtime.Caching/MemoryCache è 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 SetSizea , Size, o SizeLimit per limitare le dimensioni della cache può causare l'errore 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 viene fatto riferimento da un'app che usa 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'ora non viene memorizzata nella cache, viene creata una nuova voce che viene 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 variabile o l'ora di scadenza assoluta trascorrono trascorsi, l'elemento viene rimosso 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.

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

MemoryCacheEntryOptions

L'esempio seguente:

  • Imposta un'ora di scadenza variabile. Le richieste che accedono a questo elemento memorizzato nella cache reimposteranno l'orologio di scadenza mobile.
  • 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 memorizza principalmente nella cache le stringhe, ogni dimensione della voce della cache potrebbe essere la lunghezza della stringa.
  • L'app può specificare la dimensione di tutte le voci come 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 viene MyMemoryCache registrato con il contenitore di inserimento delle dipendenze .

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

MyMemoryCache viene creato come cache di memoria indipendente per i componenti che sono consapevoli di questa cache di dimensioni limitate e sanno come impostare la dimensione della voce della cache in modo appropriato.

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 SetSize estensione:

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 altre informazioni, vedere l'origine compatta in 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 , entrambe le voci della CancellationTokenSourcecache 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 voci della cache create all'interno del blocco erediteranno i trigger e le using 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 anche la voce e attiva una scansione per gli elementi scaduti. Nell'esempio seguente viene utilizzato CancellationTokenSource(TimeSpan) il token registrato. Quando questo token viene attivato, rimuove immediatamente la voce e attiva i callback di rimozione:

    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 secondario non è scaduto a causa della rimozione manuale o dell'aggiornamento della voce principale.

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

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

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 e quindi assegnarle alla cache solo quando sono pronte.

Risorse aggiuntive