Cache na memória no ASP.NET Core

Por Rick Anderson, John Luo e Steve Smith

O cache pode melhorar significativamente o desempenho e a escalabilidade de um aplicativo, reduzindo o trabalho necessário para gerar conteúdo. O cache funciona melhor com dados que mudam com pouca frequência e são caros de gerar. O cache faz uma cópia dos dados que podem ser retornados muito mais rápido do que da fonte. Os aplicativos devem ser escritos e testados para nunca depender de dados armazenados em cache.

ASP.NET Core suporta vários caches diferentes. A cache mais simples baseia-se na IMemoryCache interface. IMemoryCache Representa um cache armazenado na memória do servidor Web. As aplicações executadas num grupo de servidores (vários servidores) devem garantir que as sessões sejam persistentes ao usar o cache na memória. Sessões adesivas garantem que todas as solicitações de um cliente vão para o mesmo servidor. Por exemplo, uma aplicação web Azure usa Microsoft Application Request Routing (ARR) para encaminhar todos os pedidos para o mesmo servidor.

Sessões não aderentes numa web farm requerem uma cache distribuída para evitar problemas de consistência de cache. Para alguns aplicativos, um cache distribuído pode oferecer suporte a uma expansão maior do que um cache na memória. O uso de um cache distribuído descarrega a memória cache para um processo externo.

O cache na memória pode armazenar qualquer objeto. A interface de cache distribuído é limitada a byte[]. O cache na memória e o cache distribuído armazenam itens de cache como pares chave-valor.

Use System.Runtime.Caching/MemoryCache

System.Runtime.Caching / MemoryCache (pacote NuGet) pode ser usado com:

  • .NET Standard 2.0 ou posterior
  • Qualquer implementação .NET que tenha como alvo .NET Standard 2.0 ou posterior (como ASP.NET Core 3.1 ou posterior)
  • .NET Framework 4.5 ou posterior

Microsoft.Extensions.Caching.Memory/IMemoryCache (descrito neste artigo) é recomendado em vez de System.Runtime.Caching/MemoryCache porque oferece melhor integração com ASP.NET Core. Por exemplo, IMemoryCache funciona nativamente com a injeção de dependência do ASP.NET Core .

Use System.Runtime.Caching/MemoryCache como uma ponte de compatibilidade ao portar código do ASP.NET 4.x para o ASP.NET Core.

Consulte as diretrizes para cache em memória

As seguintes diretrizes aplicam-se à cache em memória:

  • O código deve sempre ter uma opção de recuo para obter dados e não depender da disponibilidade de um valor em cache.

  • A cache usa memória, que é um recurso escasso. Limitar o crescimento do cache:

    • Não insiras entradas externas na cache. Por exemplo, não é recomendado usar entrada arbitrária fornecida pelo utilizador como chave de cache porque a entrada pode consumir uma quantidade imprevisível de memória.

    • Use expirações para limitar o crescimento do cache.

    • Use SetSize, Size, e SizeLimit para limitar o tamanho do cache. O tempo de execução ASP.NET Core não limita o tamanho da cache com base na pressão da memória. O programador é responsável por limitar o tamanho da cache.

Criar uma instância de IMemoryCache

Cache em memória é um serviço que uma aplicação referencia através da injeção de dependências.

Warning

Se a mesma cache for usada por vários frameworks ou bibliotecas, é uma cache partilhada . Se usar uma cache de memória partilhada proveniente da injeção de dependências e também usar SetSize, Size, e SizeLimit para limitar o tamanho da cache, a aplicação pode falhar.

Quando é definido um limite de tamanho numa cache, todas as entradas devem especificar um tamanho quando são adicionadas. Esta abordagem pode causar problemas porque os programadores podem não ter controlo total sobre o que utiliza a cache partilhada.

Para limitar o tamanho da cache com o SetSize método, a Size propriedade ou a SizeLimit propriedade, crie um singleton de cache. Para obter mais informações e um exemplo, consulte Usar SetSize, Size, e SizeLimit para limitar o tamanho do cache.

Solicite a IMemoryCache instância no construtor:

public class IndexModel : PageModel
{
    private readonly IMemoryCache _memoryCache;

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

    // ...

O código seguinte utiliza o TryGetValue método para verificar se existe um tempo na cache. Se um instante não estiver armazenado em cache, uma nova entrada é criada e adicionada à cache com o método 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;
}

No código anterior, a entrada da cache está configurada com uma expiração deslizante de 3 segundos. Se a entrada da cache não for acedida durante mais de 3 segundos, a entrada é eliminada da cache. Cada vez que a entrada de cache é acessada, ela permanece no cache por mais 3 segundos. A CacheKeys classe faz parte do exemplo de download.

A hora atual e a hora armazenada em cache são exibidas:

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

O código a seguir usa o Set método de extensão para armazenar dados em cache por um tempo relativo sem MemoryCacheEntryOptions:

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

No código anterior, a entrada de cache é configurada com uma expiração relativa de um dia. A entrada da cache é expulsa da cache após um dia, mesmo que a entrada seja acedida durante o período de timeout.

O código seguinte utiliza os GetOrCreate métodos e GetOrCreateAsync para armazenar dados em 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);
        });

    // ...
}

O seguinte código chama o Get método para obter o tempo em cache:

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

O código a seguir obtém ou cria um item armazenado em cache com expiração absoluta:

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

Um conjunto de itens em cache com apenas uma expiração deslizante corre o risco de nunca expirar. Se o item armazenado em cache for acessado repetidamente dentro do intervalo de expiração deslizante, o item nunca expirará. Combinar uma expiração deslizante com uma expiração absoluta garante que o item expira. A expiração absoluta define um limite superior para o tempo que o item pode ser armazenado em cache. Ainda assim, permite que o item expire mais cedo, se não for solicitado dentro do período de expiração deslizante. Se passar o intervalo de expiração deslizante ou o tempo absoluto de expiração, o item é expulso da cache.

O código a seguir obtém ou cria um item em cache com expiração deslizante e expiração absoluta:

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

O código anterior garante que os dados não são armazenados em cache por mais tempo do que o tempo absoluto.

GetOrCreate, GetOrCreateAsynce Get são métodos de extensão na CacheExtensions classe. Esses métodos estendem a capacidade do IMemoryCache.

Criar MemoryCacheEntryOptions para uma entrada

O exemplo seguinte demonstra como criar MemoryCacheEntryOptions para uma entrada. O código conclui as seguintes tarefas:

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

Limite o tamanho da cache com SetSize, Size e SizeLimit

Uma MemoryCache instância pode, opcionalmente, especificar e impor um limite de tamanho. O limite de tamanho do cache não tem uma unidade de medida definida porque o cache não tem mecanismo para medir o tamanho das entradas. Se o limite de tamanho do cache estiver definido, todas as entradas deverão especificar o tamanho. O tempo de execução do ASP.NET Core não limita o tamanho do cache com base na pressão da memória. Cabe ao desenvolvedor limitar o tamanho do cache. O tamanho especificado é em unidades que o desenvolvedor escolhe.

Por exemplo:

  • Se a aplicação web armazena principalmente strings em cache, o tamanho de cada entrada no cache pode ser medido pelo comprimento da string.
  • A aplicação pode especificar o tamanho de todas as entradas como 1, e o limite de tamanho é o número de entradas.

Se a SizeLimit propriedade não estiver definida, a cache cresce sem limites. O tempo de execução do ASP.NET Core não corta o cache quando a memória do sistema está baixa. Os aplicativos devem ser projetados para:

  • Limite o crescimento do cache.
  • Chame o método Compact ou Remove quando a memória disponível for limitada.

O código seguinte cria uma instância de tamanho MemoryCache fixo sem unidade que é acessível por injeção de dependências:

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

A propriedade SizeLimit não possui unidades. As entradas em cache devem especificar o tamanho nas unidades que considerarem mais apropriadas quando o limite de tamanho da cache é definido. Todos os usuários de uma instância de cache devem usar o mesmo sistema de unidade. Uma entrada não é armazenada em cache se a soma dos tamanhos das entradas em cache exceder o valor especificado por SizeLimit. Se nenhum limite de tamanho de cache for definido, o tamanho do cache definido na entrada será ignorado.

O seguinte código regista a instância MyMemoryCache no contentor de injeção de dependências:

var builder = WebApplication.CreateBuilder(args);

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

MyMemoryCache é criado como uma cache de memória independente para componentes que estão cientes deste cache limitado por tamanho e sabem como definir adequadamente o tamanho da entrada da cache.

O tamanho da entrada de cache pode ser definido usando o SetSize método de extensão ou a Size propriedade:

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

No código anterior, as duas linhas realçadas obtêm o mesmo resultado da definição do tamanho da entrada de cache. O SetSize método é fornecido para conveniência para encadear chamadas a new MemoryCacheOptions().

Remover itens de cache com o MemoryCache.Compact

O MemoryCache.Compact método tenta remover a percentagem especificada da cache na seguinte ordem:

  • Todos os itens expirados
  • Itens por prioridade, onde os itens de menor prioridade são removidos primeiro
  • Objetos menos usados recentemente
  • Itens com a expiração absoluta mais próxima
  • Itens com a expiração deslizante mais próxima

Os itens fixados com prioridade NeverRemovenunca são removidos. O código seguinte remove um item de cache e chama o Compact método para remover 25% de entradas em cache:

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

Para obter mais informações, consulte a fonte Compact no GitHub.

Remova uma entrada no cache com dependências expiradas

O exemplo a seguir mostra como expirar uma entrada de cache se uma entrada dependente expirar. Um CancellationChangeToken é adicionado ao item armazenado em cache. Quando o Cancel método é chamado no CancellationTokenSource objeto, ambas as entradas da cache são expulsas:

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

    _memoryCache.Set(
        CacheKeys.DependentCancellationTokenSource,
        cancellationTokenSource);

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

    parentCacheEntry.Value = DateTime.Now;

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

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

    cancellationTokenSource.Cancel();
}

Quando usas um CancellationTokenSource objeto, podes expulsar múltiplas entradas de cache em grupo. Com o using padrão do código anterior, as entradas de cache criadas dentro do using âmbito herdam gatilhos e definições de expiração.

Notas de revisão sobre cache em memória

As seguintes notas aplicam-se à cache em memória:

  • A expiração não acontece em segundo plano.

    Não há temporizador que verifique ativamente o cache em busca de itens expirados. Qualquer atividade na cache (via Get, TryGetValue, Set, ou Remove) pode desencadear uma análise em segundo plano para itens expirados. Um temporizador definido no CancellationTokenSource objeto (usando o CancelAfter método) também remove a entrada e desencadeia uma varredura para itens expirados.

    O exemplo seguinte utiliza o CancellationTokenSource(TimeSpan) construtor sobrecarregado para o token registado. Quando este token é acionado, ele remove a entrada imediatamente e dispara os callbacks de expulsão.

    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 usas um callback para repovoar um item de cache:

    • Múltiplos pedidos podem descobrir que o valor da chave em cache está vazio porque o callback não está concluído.
    • Esta abordagem pode resultar em vários threads a repovoar o item em cache.
  • Quando um registo de cache (o registo pai) cria outro registo (o registo filho), o registo filho copia os tokens de expiração do registo pai e as configurações de expiração baseadas no tempo. A criança não expira por remoção manual ou atualização da entrada dos pais.

  • Use a PostEvictionCallbacks propriedade para especificar quais os callbacks que devem ser lançados após uma entrada de cache ser expulsa da cache.

  • Para a maioria das aplicações, IMemoryCache está ativado. Por exemplo, chamar AddMvc, AddControllersWithViews, AddRazorPages, AddMvcCore().AddRazorViewEngine, e muitos outros Add{Service} métodos no ficheiro Program.cs permite IMemoryCache.

    Para aplicações que não chamam um dos métodos mencionados Add{Service} , pode ser necessário chamar o AddMemoryCache método no ficheiro Program.cs .

Usar uma atualização de cache em segundo plano

Use um serviço em segundo plano, como a IHostedService interface, para atualizar a cache. O serviço em segundo plano pode recalcular as entradas e atribuí-las à cache apenas depois de estarem prontas.

Visualizar ou descarregar amostra de código (como descarregar)

Noções básicas de cache

O cache pode melhorar significativamente o desempenho e a escalabilidade de um aplicativo, reduzindo o trabalho necessário para gerar conteúdo. O cache funciona melhor com dados que mudam com pouca frequência e são caros de gerar. O cache faz uma cópia dos dados que podem ser retornados muito mais rápido do que da fonte. Os aplicativos devem ser escritos e testados para nunca depender de dados armazenados em cache.

ASP.NET Core suporta vários caches diferentes. O cache mais simples é baseado no IMemoryCache. IMemoryCache Representa um cache armazenado na memória do servidor Web. As aplicações executadas num grupo de servidores (vários servidores) devem garantir que as sessões sejam persistentes ao usar o cache na memória. Sessões adesivas garantem que as solicitações subsequentes de um cliente vão todas para o mesmo servidor. Por exemplo, os aplicativos Web do Azure usam o Roteamento de Solicitação de Aplicativo (ARR) para rotear todas as solicitações subsequentes para o mesmo servidor.

Sessões não aderentes em uma web farm exigem um cache distribuído para evitar problemas de consistência de cache. Para alguns aplicativos, um cache distribuído pode oferecer suporte a uma expansão maior do que um cache na memória. O uso de um cache distribuído descarrega a memória cache para um processo externo.

O cache na memória pode armazenar qualquer objeto. A interface de cache distribuído é limitada a byte[]. O cache na memória e o cache distribuído armazenam itens de cache como pares chave-valor.

System.Runtime.Caching/MemoryCache

System.Runtime.Caching / MemoryCache (pacote NuGet) pode ser usado com:

  • .NET Standard 2.0 ou posterior.
  • Qualquer implementação do .NET destinada ao .NET Standard 2.0 ou posterior. Por exemplo, ASP.NET Core 3.1 ou posterior.
  • .NET Framework 4.5 ou posterior.

Microsoft.Extensions.Caching.Memory/IMemoryCache (descrito neste artigo) é recomendado em relação a System.Runtime.Caching/ porque é melhor integrado no ASP.NET Core. Por exemplo, IMemoryCache funciona nativamente com a injeção de dependência do ASP.NET Core .

Use System.Runtime.Caching/MemoryCache como uma ponte de compatibilidade ao portar código do ASP.NET 4.x para o ASP.NET Core.

Diretrizes de cache

  • O código deve sempre ter uma opção de fallback para buscar dados e não depender de um valor armazenado em cache estar disponível.
  • O cache usa um recurso escasso, a memória. Limitar o crescimento do cache:
    • Não use entradas externas como chaves de cache.
    • Use expirações para limitar o crescimento do cache.
    • Use SetSize, Size, e SizeLimit para limitar o tamanho do cache. O tempo de execução do ASP.NET Core não limita o tamanho do cache com base na pressão da memória. Cabe ao desenvolvedor limitar o tamanho do cache.

Usar IMemoryCache

Warning

Usar um cache de memória partilhada da Injeção de Dependência e chamar SetSize, Size ou SizeLimit para limitar o tamanho do cache pode fazer com que a aplicação falhe. Quando um limite de tamanho é definido em um cache, todas as entradas devem especificar um tamanho ao serem adicionadas. Isso pode levar a problemas, uma vez que os desenvolvedores podem não ter controle total sobre o que usa o cache compartilhado. Ao usar SetSize, Size, ou SizeLimit para limitar o cache, crie um singleton para o cache. Para obter mais informações e um exemplo, consulte Usar SetSize, Size, e SizeLimit para limitar o tamanho do cache. Um cache compartilhado é um compartilhado por outras estruturas ou bibliotecas.

O cache na memória é um serviço referenciado a partir de um aplicativo que usa a injeção de dependência. Solicite a IMemoryCache instância no construtor:

public class HomeController : Controller
{
    private IMemoryCache _cache;

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

O código a seguir é usado TryGetValue para verificar se um horário está no cache. Se um tempo não for armazenado em cache, uma nova entrada será criada e adicionada ao cache com Set. A CacheKeys classe faz parte do exemplo de 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);
}

A hora atual e a hora armazenada em cache são exibidas:

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

O código a seguir usa o Set método extension para armazenar dados em cache por um tempo relativo sem criar o MemoryCacheEntryOptions objeto:

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

O valor armazenado DateTime em cache permanece no cache enquanto há solicitações dentro do período de tempo limite.

O código a seguir usa GetOrCreate e GetOrCreateAsync armazena dados em 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);
}

O código a seguir chama Get para buscar o tempo armazenado em cache:

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

O código a seguir obtém ou cria um item armazenado em cache com expiração absoluta:

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

    return View("Cache", cacheEntry);
}

Um conjunto de itens em cache com apenas uma expiração deslizante corre o risco de nunca expirar. Se o item armazenado em cache for acessado repetidamente dentro do intervalo de expiração deslizante, o item nunca expirará. Combine uma expiração deslizante com uma expiração absoluta para garantir que o item expire. A expiração absoluta define um limite superior sobre quanto tempo o item pode ser armazenado em cache, permitindo que o item expire mais cedo se não for solicitado dentro do intervalo de expiração deslizante. Se o intervalo de expiração deslizante ou o tempo de expiração absoluta passarem, o item será removido do cache.

O código a seguir obtém ou cria um item em cache com expiração deslizante e expiração absoluta:

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

O código anterior garante que os dados não serão armazenados em cache por mais tempo do que o tempo absoluto.

GetOrCreate, GetOrCreateAsynce Get são métodos de extensão na CacheExtensions classe. Esses métodos estendem a capacidade do IMemoryCache.

MemoryCacheEntryOptions

A seguinte amostra:

  • Define um tempo de expiração deslizante. As solicitações de acesso a esse item armazenado em cache redefinirão o relógio de expiração deslizante.
  • Define a prioridade do cache como CacheItemPriority.NeverRemove.
  • Define um PostEvictionDelegate que será chamado depois que a entrada for removida do cache. O callback é executado numa thread diferente do código que remove o item do 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);
}

Use SetSize, Size, e SizeLimit para limitar o tamanho do cache

Uma MemoryCache instância pode, opcionalmente, especificar e impor um limite de tamanho. O limite de tamanho do cache não tem uma unidade de medida definida porque o cache não tem mecanismo para medir o tamanho das entradas. Se o limite de tamanho do cache estiver definido, todas as entradas deverão especificar o tamanho. O tempo de execução do ASP.NET Core não limita o tamanho do cache com base na pressão da memória. Cabe ao desenvolvedor limitar o tamanho do cache. O tamanho especificado é em unidades que o desenvolvedor escolhe.

Por exemplo:

  • Se o aplicativo Web estivesse principalmente armazenando cadeias de caracteres em cache, cada tamanho de entrada de cache poderia ser o comprimento da cadeia de caracteres.
  • O aplicativo pode especificar o tamanho de todas as entradas como 1, e o limite de tamanho é a contagem de entradas.

Se SizeLimit não estiver definido, o cache crescerá sem limites. O tempo de execução do ASP.NET Core não corta o cache quando a memória do sistema está baixa. Os aplicativos devem ser projetados para:

  • Limite o crescimento do cache.
  • Chamada Compact ou Remove quando a memória disponível é limitada:

O código a seguir cria um tamanho MemoryCache fixo sem unidade acessível por injeção de dependência:

// 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ão tem unidades. As entradas armazenadas em cache devem especificar o tamanho nas unidades que considerarem mais apropriadas se o limite de tamanho do cache tiver sido definido. Todos os usuários de uma instância de cache devem usar o mesmo sistema de unidade. Uma entrada não será armazenada em cache se a soma dos tamanhos de entrada armazenados em cache exceder o valor especificado por SizeLimit. Se nenhum limite de tamanho de cache for definido, o tamanho do cache definido na entrada será ignorado.

O seguinte código regista MyMemoryCache no contentor de injeção de dependência.

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

MyMemoryCache é criado como um cache de memória independente para componentes que estão cientes desse tamanho de cache limitado e sabem como definir o tamanho de entrada do cache adequadamente.

O código a seguir 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");
    }
}

O tamanho da entrada de cache pode ser definido por Size ou os métodos de extensão SetSize:

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

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

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

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

    DateTime_Now = cacheEntry;

    return RedirectToPage("./Index");
}

MemoryCache.Compact

MemoryCache.Compact Tenta remover a porcentagem especificada do cache na seguinte ordem:

  • Todos os itens expirados.
  • Itens por prioridade. Os itens de prioridade mais baixa são removidos primeiro.
  • Objetos usados menos recentemente.
  • Itens com a expiração absoluta mais precoce.
  • Itens com a expiração progressiva mais precoce.

Os itens fixados com prioridade NeverRemove nunca são removidos. O código a seguir remove um item de cache e chama Compact:

_cache.Remove(MyKey);

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

Para obter mais informações, consulte a fonte Compact no GitHub.

Dependências de cache

O exemplo a seguir mostra como expirar uma entrada de cache se uma entrada dependente expirar. Um CancellationChangeToken é adicionado ao item armazenado em cache. Quando Cancel é chamado no CancellationTokenSource, ambas as entradas de cache são removidas.

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

O uso de um CancellationTokenSource permite que várias entradas de cache sejam removidas como um grupo. Com o using padrão no código acima, as entradas de cache criadas dentro do using bloco herdarão gatilhos e configurações de expiração.

Notas adicionais

  • A expiração não acontece em segundo plano. Não há nenhum temporizador que verifique ativamente o cache em busca de itens expirados. Qualquer atividade no cache (Get, Set, Remove) pode acionar uma verificação em segundo plano para itens expirados. Um temporizador no CancellationTokenSource (CancelAfter) também remove a entrada e dispara uma verificação de itens expirados. O exemplo a seguir usa CancellationTokenSource(TimeSpan) para o token registrado. Quando esse token é acionado, ele remove a entrada imediatamente e dispara os retornos de chamada de despejo:

    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);
    }
    
  • Ao usar um retorno de chamada para preencher novamente um item de cache:

    • Várias solicitações podem encontrar vazio o valor da chave em cache já que o retorno de chamada não foi concluído.
    • Isso pode resultar em vários threads preenchendo novamente o item armazenado em cache.
  • Quando uma entrada de cache é usada para criar outra, a entrada filha copia os tokens de expiração da entrada pai e as configurações de expiração baseadas no tempo. A criança não expirou por remoção manual ou atualização da entrada pai.

  • Use PostEvictionCallbacks para definir os retornos de chamada que serão disparados depois que a entrada de cache for removida do cache. No código de exemplo, CancellationTokenSource.Dispose() é chamado para liberar os recursos não gerenciados usados pelo CancellationTokenSource. No entanto, o CancellationTokenSource não é descartado imediatamente porque ainda está a ser usado pela entrada na cache. O CancellationToken é passado para MemoryCacheEntryOptions criar uma entrada de cache que expira após um determinado tempo. Portanto, Dispose não deve ser chamado até que a entrada de cache seja removida ou expirada. O código de exemplo chama o método RegisterPostEvictionCallback para registar um retorno de chamada que será invocado quando a entrada da cache for eliminada, e descarta o CancellationTokenSource nesse retorno de chamada.

  • Para a maioria das aplicações, IMemoryCache está ativado. Por exemplo, chamar AddMvc, AddControllersWithViews, AddRazorPages, AddMvcCore().AddRazorViewEngine, e muitos outros Add{Service} métodos em ConfigureServices, habilita IMemoryCache. Para aplicações que não estão a chamar um dos métodos anteriores Add{Service}, pode ser necessário chamar AddMemoryCache em ConfigureServices.

Atualização do cache em segundo plano

Use um serviço em segundo plano , como IHostedService para atualizar o cache. O serviço em segundo plano pode recalcular as entradas e, em seguida, atribuí-las ao cache somente quando estiverem prontas.

Recursos adicionais