Cache en mémoire dans ASP.NET Core

Par Rick Anderson, John Luo et Steve Smith

La mise en cache peut améliorer considérablement les performances et l’évolutivité d’une application en réduisant le travail nécessaire à la génération de contenu. La mise en cache fonctionne mieux avec des données qui changent rarement et qui sont coûteuses à générer. La mise en cache effectue une copie des données qui peut être renvoyée beaucoup plus rapidement qu’à partir de la source. Les applications doivent être écrites et testées pour ne jamais dépendre des données mises en cache.

ASP.NET Core prend en charge plusieurs caches différents. Le cache le plus simple est basé sur l’interface IMemoryCache . IMemoryCache Représente un cache stocké dans la mémoire du serveur web. Les applications exécutées sur une batterie de serveurs (plusieurs serveurs) doivent garantir que les sessions sont persistantes lors de l’utilisation du cache en mémoire. Les sessions persistantes garantissent que les requêtes d’un client sont toutes transmises au même serveur. Par exemple, une application web Azure utilise Microsoft ARR (Application Request Routing) pour acheminer toutes les requêtes vers le même serveur.

Les sessions non persistantes dans une ferme de serveurs web nécessitent un cache distribué pour éviter les problèmes de cohérence du cache. Pour certaines applications, un cache distribué peut prendre en charge un scale-out plus élevé qu’un cache en mémoire. L’utilisation d’un cache distribué décharge la mémoire cache vers un processus externe.

Le cache en mémoire peut stocker n’importe quel objet. L’interface de cache distribué est limitée à byte[]. Le cache en mémoire et le cache distribué stockent les éléments de cache sous forme de paires clé-valeur.

Utiliser System.Runtime.Caching/MemoryCache

System.Runtime.Caching / MemoryCache (Package NuGet) peut être utilisé avec :

  • .NET Standard 2.0 ou version ultérieure
  • Toute implémentation .NET qui cible .NET Standard 2.0 ou version ultérieure (par exemple, ASP.NET Core 3.1 ou version ultérieure)
  • .NET Framework 4.5 ou ultérieur

Microsoft. Extensions.Caching.Memory/IMemoryCache (décrit dans cet article) est recommandé sur System.Runtime.Caching/MemoryCache, car il offre une meilleure intégration avec ASP.NET Core. Par exemple, IMemoryCache fonctionne nativement avec l'injection de dépendances d'ASP.NET Core.

À utiliser System.Runtime.Caching/MemoryCache comme pont de compatibilité lors du portage de code de ASP.NET 4.x vers ASP.NET Core.

Passer en revue les instructions pour la mise en cache en mémoire

Les instructions suivantes s’appliquent à la mise en cache en mémoire :

  • Le code doit toujours avoir une option de secours pour extraire des données et ne pas dépendre de la disponibilité d’une valeur mise en cache.

  • Le cache utilise la mémoire, qui est une ressource rare. Limiter la croissance du cache :

    • N’insérez pas d’entrée externe dans le cache. Par exemple, l’utilisation d’une entrée fournie par l’utilisateur arbitraire comme clé de cache n’est pas recommandée, car l’entrée peut consommer une quantité imprévisible de mémoire.

    • Utilisez des expirations pour limiter la croissance du cache.

    • Utilisez SetSize, Size et SizeLimit pour limiter la taille du cache. Le runtime ASP.NET Core doesn't limite la taille du cache en fonction de la sollicitation de la mémoire. Le développeur est chargé de limiter la taille du cache.

Créer une instance d’IMemoryCache

La mise en cache en mémoire est un service auquel une application fait référence à l’aide de l’injection de dépendances.

Warning

Si le même cache est utilisé par plusieurs frameworks ou bibliothèques, il s’agit d’un cache partagé . Si vous utilisez un cache de mémoire partagée à partir de l’injection de dépendances et que vous utilisez SetSizeégalement , Sizeet SizeLimit pour limiter la taille du cache, l’application peut échouer.

Lorsqu’une limite de taille est définie sur un cache, toutes les entrées doivent spécifier une taille lorsqu’elles sont ajoutées. Cette approche peut entraîner des problèmes, car les développeurs n’ont peut-être pas un contrôle total sur ce qui utilise le cache partagé.

Pour limiter la taille du cache avec la SetSize méthode, la Size propriété ou la SizeLimit propriété, créez un singleton de cache pour la mise en cache. Pour plus d’informations et un exemple, consultez Utiliser SetSize, Size et SizeLimit pour limiter la taille du cache.

Demandez l’instance IMemoryCache dans le constructeur :

public class IndexModel : PageModel
{
    private readonly IMemoryCache _memoryCache;

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

    // ...

Le code suivant utilise la TryGetValue méthode pour vérifier si une heure se trouve dans le cache. Si une heure n'est pas mise en cache, une nouvelle entrée est créée et ajoutée au cache avec la Set méthode suivante :

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

Dans le code précédent, l’entrée de cache est configurée avec une expiration glissante de 3 secondes. Si l’entrée du cache n’est pas accessible pendant plus de 3 secondes, l’entrée est supprimée du cache. Chaque fois que l’entrée du cache est accédée, elle reste dans le cache pendant 3 secondes supplémentaires. La CacheKeys classe fait partie de l’exemple de téléchargement.

L’heure actuelle et l’heure mise en cache sont affichées :

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

Le code suivant utilise la méthode d’extension Set pour mettre en cache les données pendant une durée relative sans MemoryCacheEntryOptions.

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

Dans le code précédent, l’entrée de cache est configurée avec une expiration relative d’un jour. L’entrée de cache est supprimée du cache après un jour, même si l’entrée est accessible pendant la période d’expiration.

Le code suivant utilise les méthodes GetOrCreate et GetOrCreateAsync pour mettre en cache les données.

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

    // ...
}

Le code suivant appelle la Get méthode pour récupérer l’heure mise en cache :

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

Le code suivant obtient ou crée un élément mis en cache avec une expiration absolue :

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

Un ensemble d’éléments mis en cache avec une expiration glissante uniquement risque de ne jamais expirer. Si l’élément mis en cache est consulté à plusieurs reprises dans l’intervalle d’expiration glissant, l’élément n’expire jamais. La combinaison d’une expiration glissante avec une expiration absolue garantit l’expiration de l’élément. L’expiration absolue définit une limite supérieure sur la durée pendant laquelle l’élément peut être mis en cache. Il permet toujours à l’élément d’expirer précédemment, si l’élément n’est pas demandé dans l’intervalle d’expiration glissant. Si l’intervalle d’expiration glissant ou le délai d’expiration absolu passe, l’élément est supprimé du cache.

Le code suivant obtient ou crée un élément mis en cache avec une expiration glissante et absolue :

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

Le code précédent garantit que les données ne sont pas mises en cache plus longtemps que le temps absolu.

GetOrCreate, GetOrCreateAsync, et Get sont des méthodes d’extension dans la classe CacheExtensions. Ces méthodes étendent la capacité de IMemoryCache.

Créer MemoryCacheEntryOptions pour une entrée

L’exemple suivant montre comment créer MemoryCacheEntryOptions pour une entrée. Le code effectue les tâches suivantes :

  • Définit la priorité du cache sur CacheItemPriority.NeverRemove.

  • Définit un PostEvictionDelegate à appeler après que l’entrée a été supprimée du cache. Le rappel s’exécute sur un thread différent du code qui supprime l’élément du 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}.");
}

Limiter la taille du cache avec SetSize, Size et SizeLimit

Une MemoryCache instance peut éventuellement spécifier et appliquer une limite de taille. La limite de taille du cache n’a pas d’unité de mesure définie, car le cache ne dispose d’aucun mécanisme pour mesurer la taille des entrées. Si la limite de taille du cache est définie, toutes les entrées doivent spécifier la taille. Le runtime ASP.NET Core ne limite pas la taille du cache en fonction de la sollicitation de la mémoire. C’est au développeur de limiter la taille du cache. La taille spécifiée est exprimée en unités choisies par le développeur.

Par exemple:

  • Si l’application web met principalement en cache les chaînes, chaque taille d’entrée de cache peut être la longueur de la chaîne.
  • L’application peut spécifier la taille de toutes les entrées comme 1, et la limite de taille est le nombre d’entrées.

Si la SizeLimit propriété n’est pas définie, le cache augmente sans limite. Le runtime ASP.NET Core ne réduit pas le cache lorsque la mémoire système est faible. L’architecture des applications doit être conforme aux critères suivants :

  • Limitez la croissance du cache.
  • Appelez la méthode Compact ou Remove lorsque la mémoire disponible est limitée.

Le code suivant crée une instance de taille MemoryCache fixe sans unité accessible par injection de dépendances :

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

La SizeLimit propriété n’a pas d’unités. Les entrées mises en cache doivent spécifier la taille dans les unités qu’elles considèrent le plus appropriée lorsque la limite de taille du cache est définie. Tous les utilisateurs d’une instance de cache doivent utiliser le même système d’unités. Une entrée n’est pas mise en cache si la somme des tailles d’entrée mises en cache dépasse la valeur spécifiée par SizeLimit. Si aucune limite de taille de cache n’est définie, la taille de cache définie sur l’entrée est ignorée.

Le code suivant inscrit l’instance MyMemoryCache auprès du conteneur d’injection de dépendances :

var builder = WebApplication.CreateBuilder(args);

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

MyMemoryCache est créé en tant que cache de mémoire indépendant pour les composants qui connaissent ce cache de taille limitée et savent comment définir la taille d’entrée du cache de manière appropriée.

La taille de l’entrée de cache peut être définie à l’aide de la SetSize méthode d’extension ou de la Size propriété :

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

Dans le code précédent, les deux lignes en surbrillance obtiennent le même résultat en définissant la taille de l’entrée de cache. La SetSize méthode est fournie pour des raisons pratiques lors du chaînage des appels sur new MemoryCacheOptions().

Supprimer des éléments de cache avec MemoryCache.Compact

La MemoryCache.Compact méthode tente de supprimer le pourcentage spécifié du cache dans l’ordre suivant :

  • Tous les éléments expirés
  • Éléments par priorité, où les éléments de priorité les plus bas sont supprimés en premier
  • Objets les moins récemment utilisés
  • Éléments avec l’expiration absolue la plus ancienne
  • Éléments avec l’expiration glissante la plus ancienne

Les éléments épinglés avec la priorité NeverRemovene sont jamais supprimés. Le code suivant supprime un élément de cache et appelle la Compact méthode pour supprimer 25% d’entrées mises en cache :

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

Pour plus d’informations, consultez la source Compact sur GitHub.

Supprimer une entrée de cache avec des dépendances expirées

L’exemple suivant montre comment faire expirer une entrée de cache si une entrée dépendante expire. Un CancellationChangeToken est ajouté à l’élément mis en cache. Lorsque la Cancel méthode est appelée sur l’objet, les deux entrées du CancellationTokenSource cache sont supprimées :

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

Lorsque vous utilisez un CancellationTokenSource objet, vous pouvez supprimer plusieurs entrées de cache en tant que groupe. Avec le using modèle dans le code précédent, les entrées de cache créées à l’intérieur de l’étendue using héritent des déclencheurs et des paramètres d’expiration.

Passer en revue les notes relatives à la mise en cache en mémoire

Les notes suivantes s’appliquent à la mise en cache en mémoire :

  • L’expiration ne se produit pas en arrière-plan.

    Il n’y a pas de minuterie qui analyse activement le cache à la recherche d’éléments expirés. Toute activité sur le cache (via Get, , TryGetValueou SetRemove) peut déclencher une analyse en arrière-plan pour les éléments expirés. Un minuteur défini sur l’objet CancellationTokenSource (à l’aide de la CancelAfter méthode) supprime également l’entrée et déclenche une analyse des éléments expirés.

    L’exemple suivant utilise le CancellationTokenSource(TimeSpan) constructeur surchargé pour le jeton enregistré. Lorsque ce jeton est déclenché, il supprime immédiatement l’entrée et lance les rappels d’expulsion :

    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);
    }
    
  • Lorsque vous utilisez un rappel pour remplir à nouveau un élément de cache :

    • Plusieurs requêtes peuvent découvrir que la valeur de clé mise en cache est vide, car le rappel n’est pas terminé.
    • Cette approche peut entraîner la mise à jour de l'élément mis en cache par plusieurs threads.
  • Lorsqu’une entrée de cache (le parent) crée une autre entrée (l’enfant), l’enfant copie les jetons d’expiration de l’entrée parente et les paramètres d’expiration basés sur le temps. L'enfant ne expire pas par suppression manuelle ou mise à jour de l'entrée du parent (parent entry).

  • Utilisez la PostEvictionCallbacks propriété pour spécifier les rappels à déclencher une fois qu’une entrée de cache est supprimée du cache.

  • Pour la plupart des applications, IMemoryCache est activé. Par exemple, l'appel des méthodes AddMvc, AddControllersWithViews, AddRazorPages, AddMvcCore().AddRazorViewEngine, et de nombreuses autres Add{Service} dans le fichier Program.cs active IMemoryCache.

    Pour les applications qui n’appellent pas l’une des méthodes mentionnées Add{Service} , vous devrez peut-être appeler la AddMemoryCache méthode dans le fichier Program.cs .

Utiliser une mise à jour du cache en arrière-plan

Utilisez un service en arrière-plan tel que l’interface IHostedService pour mettre à jour le cache. Le service en arrière-plan peut recompiler les entrées et les affecter au cache uniquement une fois qu’elles sont prêtes.

Afficher ou télécharger un exemple de code (comment télécharger)

Principes de base de la mise en cache

La mise en cache peut améliorer considérablement les performances et l’évolutivité d’une application en réduisant le travail nécessaire à la génération de contenu. La mise en cache fonctionne mieux avec des données qui changent rarement et qui sont coûteuses à générer. La mise en cache effectue une copie des données qui peut être renvoyée beaucoup plus rapidement qu’à partir de la source. Les applications doivent être écrites et testées pour ne jamais dépendre des données mises en cache.

ASP.NET Core prend en charge plusieurs caches différents. Le cache le plus simple est basé sur le IMemoryCache. IMemoryCache Représente un cache stocké dans la mémoire du serveur web. Les applications exécutées sur une batterie de serveurs (plusieurs serveurs) doivent garantir que les sessions sont persistantes lors de l’utilisation du cache en mémoire. Les sessions persistantes garantissent que les requêtes ultérieures d’un client sont toutes transmises au même serveur. Par exemple, Azure Web Apps utilise le routage des demandes d’application (ARR) pour acheminer toutes les demandes suivantes vers le même serveur.

Les sessions non persistantes dans une batterie de serveurs Web nécessitent un cache distribué pour éviter les problèmes de cohérence du cache. Pour certaines applications, un cache distribué peut prendre en charge un scale-out plus élevé qu’un cache en mémoire. L’utilisation d’un cache distribué décharge la mémoire cache vers un processus externe.

Le cache en mémoire peut stocker n’importe quel objet. L’interface de cache distribué est limitée à byte[]. Le cache en mémoire et le cache distribué stockent les éléments de cache sous forme de paires clé-valeur.

System.Runtime.Caching/MemoryCache

System.Runtime.Caching / MemoryCache (Package NuGet) peut être utilisé avec :

  • .NET Standard 2.0 ou version ultérieure.
  • Toute implémentation .NET qui cible .NET Standard 2.0 ou version ultérieure. Par exemple, ASP.NET Core 3.1 ou version ultérieure.
  • .NET Framework 4.5 ou version ultérieure.

Microsoft.Extensions.Caching.Memory/IMemoryCache (décrit dans cet article) est recommandé carSystem.Runtime.Caching/MemoryCacheil est mieux intégré dans ASP.NET Core. Par exemple, IMemoryCache fonctionne nativement avec l'injection de dépendances d'ASP.NET Core.

À utiliser System.Runtime.Caching/MemoryCache comme pont de compatibilité lors du portage de code de ASP.NET 4.x vers ASP.NET Core.

Recommandations en matière de cache

  • Le code doit toujours avoir une option de secours pour récupérer les données et ne pas dépendre de la disponibilité d’une valeur mise en cache.
  • Le cache utilise une ressource rare, la mémoire. Limiter la croissance du cache :
    • N’utilisez pas d’entrée externe comme clé de cache.
    • Utilisez des expirations pour limiter la croissance du cache.
    • Utilisez SetSize, Size et SizeLimit pour limiter la taille du cache. Le runtime ASP.NET Core ne limite pas la taille du cache en fonction de la sollicitation de la mémoire. C’est au développeur de limiter la taille du cache.

Utiliser IMemoryCache

Warning

L’utilisation d’un cache mémoire partagé via l’injection de dépendances et l’appel SetSize à , Size, ou SizeLimit pour limiter la taille du cache peut entraîner l’échec de l’application. Lorsqu’une limite de taille est définie sur un cache, toutes les entrées doivent spécifier une taille lors de l’ajout. Cela peut entraîner des problèmes, car les développeurs peuvent ne pas avoir un contrôle total sur ce qui utilise le cache partagé. Lorsque vous utilisez SetSize, Sizeou SizeLimit pour limiter le cache, créez un singleton de cache pour la mise en cache. Pour plus d’informations et un exemple, consultez Utiliser SetSize, Size et SizeLimit pour limiter la taille du cache. Un cache partagé est un cache partagé par d’autres frameworks ou bibliothèques.

La mise en cache en mémoire est un service référencé depuis une application utilisant l'injectionde dépendances. Demandez l’instance IMemoryCache dans le constructeur :

public class HomeController : Controller
{
    private IMemoryCache _cache;

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

Le code suivant sert TryGetValue à vérifier si une heure est dans le cache. Si une heure n'est pas mise en cache, une nouvelle entrée est créée et ajoutée au cache avec Set. La CacheKeys classe fait partie de l’exemple de téléchargement.

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

L’heure actuelle et l’heure mise en cache sont affichées :

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

Le code suivant utilise la méthode d’extension pour mettre en cache les Set données pendant une durée relative sans créer l’objet 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);
}

La valeur mise DateTime en cache reste dans le cache tant qu’il y a des demandes pendant la période d’expiration.

Le code suivant utilise GetOrCreate et GetOrCreateAsync pour mettre en cache les données.

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

Le code suivant appelle Get afin de récupérer l’heure mise en cache :

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

Le code suivant obtient ou crée un élément mis en cache avec une expiration absolue :

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

    return View("Cache", cacheEntry);
}

Un ensemble d’éléments mis en cache avec une expiration glissante uniquement risque de ne jamais expirer. Si l’élément mis en cache est consulté à plusieurs reprises dans l’intervalle d’expiration glissant, l’élément n’expire jamais. Combinez une date d’expiration glissante avec une date d’expiration absolue pour garantir l’expiration de l’article. L’expiration absolue définit une limite supérieure sur la durée de mise en cache de l’élément, tout en permettant à l’élément d’expirer plus tôt s’il n’est pas demandé dans l’intervalle d’expiration glissant. Si l’intervalle d’expiration glissant ou le délai d’expiration absolu est dépassé, l’élément est supprimé du cache.

Le code suivant obtient ou crée un élément mis en cache avec une expiration glissante et absolue :

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

Le code précédent garantit que les données ne seront pas mises en cache plus longtemps que la durée absolue.

GetOrCreate, GetOrCreateAsync, et Get sont des méthodes d’extension dans la classe CacheExtensions. Ces méthodes étendent la capacité de IMemoryCache.

MemoryCacheEntryOptions

L’exemple suivant :

  • Définit un délai d’expiration glissant. Les demandes qui accèdent à cet élément mis en cache réinitialisent l’horloge d’expiration glissante.
  • Définit la priorité du cache sur CacheItemPriority.NeverRemove.
  • Définit un PostEvictionDelegate qui sera appelé après l’élimination de l’entrée du cache. Le rappel est exécuté sur un thread différent du code qui supprime l’élément du 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);
}

Utilisez SetSize, Size et SizeLimit pour limiter la taille du cache

Une MemoryCache instance peut éventuellement spécifier et appliquer une limite de taille. La limite de taille du cache n’a pas d’unité de mesure définie, car le cache ne dispose d’aucun mécanisme pour mesurer la taille des entrées. Si la limite de taille du cache est définie, toutes les entrées doivent spécifier la taille. Le runtime ASP.NET Core ne limite pas la taille du cache en fonction de la sollicitation de la mémoire. C’est au développeur de limiter la taille du cache. La taille spécifiée est exprimée en unités choisies par le développeur.

Par exemple:

  • Si l’application web mettait principalement en cache des chaînes, la taille de chaque entrée de cache pouvait correspondre à la longueur de la chaîne.
  • L’application peut spécifier la taille de toutes les entrées sur 1, et la limite de taille est le nombre d’entrées.

Si SizeLimit n’est pas défini, le cache augmente sans limite. Le runtime ASP.NET Core ne réduit pas le cache lorsque la mémoire système est faible. L’architecture des applications doit être conforme aux critères suivants :

  • Limitez la croissance du cache.
  • Appelez Compact ou Remove lorsque la mémoire disponible est limitée :

Le code suivant crée une taille MemoryCache fixe sans unité accessible par injection de dépendances :

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

SizeLimit n’a pas d’unités. Les entrées mises en cache doivent spécifier la taille dans les unités qu’elles jugent les plus appropriées si la limite de taille du cache a été définie. Tous les utilisateurs d’une instance de cache doivent utiliser le même système d’unités. Une entrée n’est pas mise en cache si la somme des tailles d’entrée mises en cache dépasse la valeur spécifiée par SizeLimit. Si aucune limite de taille de cache n’est définie, la taille de cache définie sur l’entrée sera ignorée.

Le code suivant s’enregistre MyMemoryCache dans le conteneur d’injection de dépendances.

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

MyMemoryCache est créé en tant que cache mémoire indépendant pour les composants qui sont conscients de cette taille de cache limitée et qui savent comment définir correctement la taille d’entrée de cache.

Le code suivant utilise 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 taille de l’entrée du cache peut être définie par Size ou les méthodes d’extension 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 tente de supprimer le pourcentage spécifié du cache dans l’ordre suivant :

  • Tous les articles expirés.
  • Éléments par priorité. Les éléments les moins prioritaires sont supprimés en premier.
  • Objets les moins récemment utilisés.
  • Articles dont l’expiration absolue est la plus proche.
  • Les articles avec la plus ancienne expiration glissante.

Les éléments épinglés avec la priorité NeverRemove ne sont jamais supprimés. Le code suivant supprime un élément de cache et appelle Compact:

_cache.Remove(MyKey);

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

Pour plus d’informations, consultez la source Compact sur GitHub.

Dépendances de cache

L’exemple suivant montre comment faire expirer une entrée de cache si une entrée dépendante expire. Un CancellationChangeToken est ajouté à l’élément mis en cache. Lorsque Cancel est appelé sur le CancellationTokenSource, les deux entrées du cache sont supprimées.

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’utilisation de a CancellationTokenSource permet de supprimer plusieurs entrées de cache en tant que groupe. Avec le using modèle dans le code ci-dessus, les entrées de cache créées à l’intérieur du using bloc hériteront des déclencheurs et des paramètres d’expiration.

Remarques supplémentaires

  • L’expiration ne se produit pas en arrière-plan. Il n’y a pas de minuterie qui analyse activement le cache à la recherche d’éléments expirés. Toute activité sur le cache (Get, Set, Remove) peut déclencher une analyse en arrière-plan des éléments expirés. Un minuteur sur le CancellationTokenSource (CancelAfter) supprime également l’entrée et déclenche un scan pour les objets périmés. L’exemple suivant utilise CancellationTokenSource(TimeSpan) pour le jeton enregistré. Lorsque ce jeton est déclenché, il supprime immédiatement l’entrée et lance les rappels d’expulsion :

    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);
    }
    
  • Lors de l’utilisation d’un callback pour remplir à nouveau un élément de cache :

    • Plusieurs requêtes peuvent trouver la valeur de clé mise en cache vide car le rappel n’est pas terminé.
    • Cela peut entraîner plusieurs threads à réapprovisionner l'élément mis en cache.
  • Lorsqu'une entrée de cache est utilisée pour en créer une autre, l'enfant copie les jetons d'expiration de l'entrée parent et les paramètres d'expiration basés sur le temps. L'enfant n'est pas périmé par suppression manuelle ou mise à jour de l'entrée parentale.

  • Utilisez PostEvictionCallbacks pour définir les callbacks qui seront déclenchés après l’évacuation de l’entrée de cache. Dans l’exemple de code, CancellationTokenSource.Dispose() est appelé pour libérer les ressources non gérées utilisées par le CancellationTokenSource. Cependant, le CancellationTokenSource n'est pas supprimé immédiatement, car il est toujours utilisé par l'entrée de cache. Le CancellationToken est passé à MemoryCacheEntryOptions pour créer une entrée de cache qui expire après un certain temps. Il ne doit donc Dispose pas être appelé tant que l’entrée de cache n’est pas supprimée ou n’a pas expiré. Le code d’exemple appelle la RegisterPostEvictionCallback méthode pour enregistrer un callback qui sera invoqué lorsque l’entrée de cache est évincée, et il élimine le CancellationTokenSource dans ce callback.

  • Pour la plupart des applications, IMemoryCache est activé. Par exemple, l’appel de AddMvc, AddControllersWithViews, AddRazorPages, AddMvcCore().AddRazorViewEngine, et de nombreuses autres méthodes dans ConfigureServices, permet d’activer IMemoryCache. Pour les applications qui n’appellent pas l’une des méthodes précédentes Add{Service}, il peut être nécessaire d’appeler AddMemoryCache en ConfigureServices.

Mise à jour du cache d’arrière-plan

Utilisez un service d’arrière-plan tel que IHostedService pour mettre à jour le cache. Le service d’arrière-plan peut recalculer les entrées, puis les affecter au cache uniquement lorsqu’elles sont prêtes.

Ressources supplémentaires