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 contenuto. La memorizzazione nella cache funziona meglio con i dati che cambiano raramente ed è costoso da generare. La memorizzazione nella cache crea una copia dei dati che possono essere restituiti molto più velocemente rispetto all'origine. Le app devono essere scritte e testate per 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 archiviata 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 da un client vadano tutti 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 un aumento del numero di istanze superiore rispetto a una cache in memoria. L'uso di una cache distribuita scarica la memoria della cache in un processo esterno.

La cache in memoria può archiviare qualsiasi oggetto. L'interfaccia della cache distribuita è limitata a byte[]. Gli elementi della cache in memoria e 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 di .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 versioni successive.

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

Usare 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 avere sempre un'opzione di fallback per recuperare i dati e non dipendere da un valore memorizzato nella cache disponibile.
  • La cache usa una risorsa insufficiente, memoria. 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.
    • Usare le scadenze per limitare l'aumento della cache.
    • Usare SetSize, Size e SizeLimit per limitare le dimensioni della cache. Il runtime di ASP.NET Core non limita le dimensioni della cache in base alla pressione della memoria. Spetta allo sviluppatore limitare le dimensioni della cache.

Usare IMemoryCache

Avviso

L'uso di una cache di memoria condivisa da Inserimento dipendenze e chiamata SetSizea , Sizeo SizeLimit per limitare le dimensioni della cache può causare un errore dell'app. Quando viene impostato un limite di dimensioni in una cache, tutte le voci devono specificare una dimensione quando vengono aggiunte. Ciò può causare problemi perché gli sviluppatori potrebbero non avere il controllo completo su ciò che usa la cache condivisa. Quando si usa SetSize, Sizeo SizeLimit per limitare la cache, creare un singleton della cache per la memorizzazione nella cache. Per altre informazioni e un esempio, vedere Usare SetSize, Size e SizeLimit per limitare le dimensioni della cache. Una cache condivisa è condivisa da altri framework o librerie.

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

public class IndexModel : PageModel
{
    private readonly IMemoryCache _memoryCache;

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

    // ...

Il codice seguente usa TryGetValue per verificare se un'ora si trova nella cache. Se non viene memorizzata nella cache, viene creata una nuova voce e 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 viene configurata con una scadenza scorrevole di tre secondi. Se la voce della cache non è accessibile per più di tre secondi, viene rimossa dalla cache. Ogni volta che si accede alla voce della cache, rimane nella cache per altri 3 secondi. La CacheKeys classe fa parte dell'esempio di download.

Vengono visualizzati 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 usa il Set metodo di 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 viene configurata con una scadenza relativa di un giorno. La voce della cache viene rimossa dalla cache dopo un giorno, anche se è accessibile entro questo periodo di timeout.

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

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 scorrevole rischia di non scadere mai. Se l'elemento memorizzato nella cache viene eseguito ripetutamente all'interno dell'intervallo di scadenza scorrevole, l'elemento non scade mai. Combinare una scadenza scorrevole con una scadenza assoluta per garantire la scadenza dell'elemento. La scadenza assoluta imposta un limite superiore per quanto tempo l'elemento può essere memorizzato nella cache, consentendo comunque la scadenza dell'elemento in precedenza se non viene richiesto entro l'intervallo di scadenza scorrevole. Se l'intervallo di scadenza scorrevole o la scadenza assoluta passano, 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, GetOrCreateAsynce Get sono metodi di estensione nella CacheExtensions classe . Questi metodi estendono la funzionalità di IMemoryCache.

MemoryCacheEntryOptions

L'esempio seguente:

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

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

Un'istanza MemoryCache può facoltativamente specificare e applicare un limite di dimensioni. Il limite di dimensioni della cache non ha un'unità di misura definita perché la cache non ha alcun meccanismo per misurare le dimensioni delle voci. Se è impostato il limite di dimensioni della cache, tutte le voci devono specificare le dimensioni. Il runtime di ASP.NET Core non limita le dimensioni della cache in base alla pressione della memoria. Spetta allo sviluppatore limitare le dimensioni della cache. Le dimensioni specificate sono in unità scelte dallo sviluppatore.

Ad esempio:

  • Se l'app Web era principalmente la memorizzazione nella cache delle stringhe, ogni dimensione della voce della cache potrebbe essere la lunghezza della stringa.
  • L'app può specificare le dimensioni di tutte le voci come 1 e il limite di dimensioni è il numero di voci.

Se SizeLimit non è impostato, la cache aumenta senza limiti. Il runtime di ASP.NET Core non taglia la cache quando la memoria di sistema è insufficiente. Le app devono essere architettate 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 dall'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 le dimensioni in qualsiasi unità che considerino più appropriate se è stato impostato il limite di dimensioni della cache. Tutti gli utenti di un'istanza della cache devono usare 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 viene impostato alcun limite di dimensioni della cache, le dimensioni della cache impostate nella voce vengono ignorate.

Il codice seguente viene registrato MyMemoryCache 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 conoscono questa cache limitata di dimensioni e sanno come impostare le dimensioni della voce della cache in modo appropriato.

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. SetSize viene fornito per praticità durante il concatenamento delle chiamate a 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 usati meno di recente.
  • Elementi con scadenza assoluta meno recente.
  • Elementi con la scadenza scorrevole meno recente.

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

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

Per altre informazioni, vedere l'origine Compact in GitHub.

Dipendenze della cache

Nell'esempio seguente viene illustrato come scadere una voce della cache se una voce dipendente scade. Un CancellationChangeToken oggetto viene aggiunto all'elemento memorizzato nella cache. Quando Cancel viene chiamato in , 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'uso di consente CancellationTokenSource la rimozione di 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.

Note aggiuntive

  • La scadenza non viene eseguita in background. Non esiste alcun timer che analizza attivamente la cache per individuare gli elementi scaduti. Qualsiasi attività nella cache (Get, Set, Remove) può attivare un'analisi in background per gli elementi scaduti. Un timer in CancellationTokenSource (CancelAfter) rimuove anche la voce e attiva un'analisi degli elementi scaduti. Nell'esempio seguente viene CancellationTokenSource(TimeSpan) usato per 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 usa un callback per ripopolare un elemento della cache:

    • Più richieste possono trovare il valore della chiave memorizzata nella cache vuoto perché il callback non è stato completato.
    • Ciò può comportare la ripopolazione dell'elemento memorizzato nella cache da parte di diversi thread.
  • Quando viene usata una voce della cache per creare un'altra, il figlio copia i token di scadenza della voce padre e le impostazioni di scadenza basate sul tempo. L'elemento figlio non è scaduto dalla rimozione manuale o dall'aggiornamento della voce padre.

  • Utilizzare PostEvictionCallbacks per impostare i callback che verranno attivati dopo la rimozione della voce della cache dalla cache.

  • Per la maggior parte delle app, IMemoryCache è abilitato. Ad esempio, la chiamata AddMvca , AddRazorPagesAddControllersWithViews, AddMvcCore().AddRazorViewEngine, e molti altri Add{Service} metodi in Program.cs, abilita IMemoryCache. Per le app che non chiamano uno dei metodi precedenti Add{Service} , potrebbe essere necessario chiamare AddMemoryCache in Program.cs.

Aggiornamento della cache in background

Usare un servizio in background, IHostedService ad esempio per aggiornare la cache. Il servizio in background può ricompilare le voci e quindi assegnarle alla cache solo quando sono pronte.

Risorse aggiuntive

Visualizzare o scaricare il codice di esempio (procedura per il download)

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 contenuto. La memorizzazione nella cache funziona meglio con i dati che cambiano raramente ed è costoso da generare. La memorizzazione nella cache crea una copia dei dati che possono essere restituiti molto più velocemente rispetto all'origine. Le app devono essere scritte e testate per 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 archiviata 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 tutte inviate 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 un aumento del numero di istanze superiore rispetto a una cache in memoria. L'uso di una cache distribuita scarica la memoria della cache in un processo esterno.

La cache in memoria può archiviare qualsiasi oggetto. L'interfaccia della cache distribuita è limitata a byte[]. Gli elementi della cache in memoria e 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 di .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 versioni successive.

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

Usare 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 avere sempre un'opzione di fallback per recuperare i dati e non dipendere da un valore memorizzato nella cache disponibile.
  • La cache usa una risorsa insufficiente, memoria. Limitare la crescita della cache:
    • Non usare l'input esterno come chiavi della cache.
    • Usare le scadenze per limitare l'aumento della cache.
    • Usare SetSize, Size e SizeLimit per limitare le dimensioni della cache. Il runtime di ASP.NET Core non limita le dimensioni della cache in base alla pressione della memoria. Spetta allo sviluppatore limitare le dimensioni della cache.

Usare IMemoryCache

Avviso

L'uso di una cache di memoria condivisa da Inserimento dipendenze e chiamata SetSizea , Sizeo SizeLimit per limitare le dimensioni della cache può causare un errore dell'app. Quando viene impostato un limite di dimensioni in una cache, tutte le voci devono specificare una dimensione quando vengono aggiunte. Ciò può causare problemi perché gli sviluppatori potrebbero non avere il controllo completo su ciò che usa la cache condivisa. Quando si usa SetSize, Sizeo SizeLimit per limitare la cache, creare un singleton della cache per la memorizzazione nella cache. Per altre informazioni e un esempio, vedere Usare SetSize, Size e SizeLimit per limitare le dimensioni della cache. Una cache condivisa è condivisa da altri framework o librerie.

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

public class HomeController : Controller
{
    private IMemoryCache _cache;

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

Il codice seguente usa TryGetValue per verificare se un'ora si trova nella cache. Se non viene memorizzata 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 visualizzati 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>

Il codice seguente usa il Set metodo di estensione per memorizzare nella cache i 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 mentre sono presenti richieste entro il periodo di timeout.

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

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 scorrevole rischia di non scadere mai. Se l'elemento memorizzato nella cache viene eseguito ripetutamente all'interno dell'intervallo di scadenza scorrevole, l'elemento non scade mai. Combinare una scadenza scorrevole con una scadenza assoluta per garantire la scadenza dell'elemento. La scadenza assoluta imposta un limite superiore per quanto tempo l'elemento può essere memorizzato nella cache, consentendo comunque la scadenza dell'elemento in precedenza se non viene richiesto entro l'intervallo di scadenza scorrevole. Se l'intervallo di scadenza scorrevole o la scadenza assoluta passano, 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, GetOrCreateAsynce Get sono metodi di estensione nella CacheExtensions classe . Questi metodi estendono la funzionalità di IMemoryCache.

MemoryCacheEntryOptions

L'esempio seguente:

  • Imposta un'ora di scadenza scorrevole. Le richieste che accedono a questo elemento memorizzato nella cache reimpostano l'orologio di scadenza scorrevole.
  • Imposta la priorità della cache su CacheItemPriority.NeverRemove.
  • Imposta un PostEvictionDelegate oggetto che verrà chiamato dopo la rimozione della voce 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);
}

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

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

Ad esempio:

  • Se l'app Web era principalmente la memorizzazione nella cache delle stringhe, ogni dimensione della voce della cache potrebbe essere la lunghezza della stringa.
  • L'app può specificare le dimensioni di tutte le voci come 1 e il limite di dimensioni è il numero di voci.

Se SizeLimit non è impostato, la cache aumenta senza limiti. Il runtime di ASP.NET Core non taglia la cache quando la memoria di sistema è insufficiente. Le app devono essere architettate 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 dall'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 le dimensioni in qualsiasi unità che ritengono più appropriate se è stato impostato il limite di dimensioni della cache. Tutti gli utenti di un'istanza della cache devono usare 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 viene impostato alcun limite di dimensioni della cache, le dimensioni della cache impostate nella voce verranno ignorate.

Il codice seguente viene registrato MyMemoryCache 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 conoscono questa cache limitata di dimensioni e sanno come impostare le dimensioni 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");
    }
}

Le dimensioni della voce della cache possono essere impostate tramite Size o i 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 usati meno di recente.
  • Elementi con scadenza assoluta meno recente.
  • Elementi con la scadenza scorrevole meno recente.

Gli elementi aggiunti 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 Compact in GitHub.

Dipendenze della cache

Nell'esempio seguente viene illustrato come scadere una voce della cache se una voce dipendente scade. Un CancellationChangeToken oggetto viene aggiunto all'elemento memorizzato nella cache. Quando Cancel viene chiamato in , 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'uso di consente CancellationTokenSource la rimozione di più voci della cache come gruppo. Con il using modello nel codice precedente, le voci della cache create all'interno del using blocco erediteranno i trigger e le impostazioni di scadenza.

Note aggiuntive

  • La scadenza non viene eseguita in background. Non esiste alcun timer che analizza attivamente la cache per gli elementi scaduti. Qualsiasi attività nella cache (Get, Set, Remove) può attivare un'analisi in background per gli elementi scaduti. Un timer in CancellationTokenSource (CancelAfter) rimuove anche la voce e attiva un'analisi degli elementi scaduti. Nell'esempio seguente viene CancellationTokenSource(TimeSpan) usato per 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 usa un callback per ripopolare un elemento della cache:

    • Più richieste possono trovare il valore della chiave memorizzata nella cache vuoto perché il callback non è stato completato.
    • Ciò può comportare la ripopolazione dell'elemento memorizzato nella cache da parte di diversi thread.
  • Quando viene usata una voce della cache per creare un'altra, il figlio copia i token di scadenza della voce padre e le impostazioni di scadenza basate sul tempo. L'elemento figlio non è scaduto dalla rimozione manuale o dall'aggiornamento della voce padre.

  • Utilizzare PostEvictionCallbacks per impostare i callback che verranno attivati dopo la rimozione della voce della cache dalla cache. Nel codice CancellationTokenSource.Dispose() di esempio viene chiamato per rilasciare le risorse non gestite usate da CancellationTokenSource. Tuttavia, l'oggetto CancellationTokenSource non viene eliminato immediatamente perché è ancora in uso dalla voce della cache. Viene CancellationToken passato a MemoryCacheEntryOptions per creare una voce della cache che scade dopo un determinato periodo di tempo. Pertanto 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 ed elimina in CancellationTokenSource tale callback.

  • Per la maggior parte delle app, IMemoryCache è abilitato. Ad esempio, la chiamata AddMvca , AddRazorPagesAddControllersWithViews, AddMvcCore().AddRazorViewEngine, e molti altri Add{Service} metodi in ConfigureServices, 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

Usare un servizio in background, IHostedService ad esempio per aggiornare la cache. Il servizio in background può ricompilare le voci e quindi assegnarle alla cache solo quando sono pronte.

Risorse aggiuntive