Caching do .NET Framework


Desde a primeira versão do ASP.NET, a Microsoft incorporou nele recursos de caching (System.Web.Caching), com a finalidade de colocar em memória, informações que são custosas o bastante para carregar e/ou montar a todo momento, ou seja, apenas a primeira requisição pagaria o preço da carga/montagem, e as requisições subsequentes, passam a utilizar a informação que já está em memória, melhorando consideravelmente a performance.

O maior problema daquela API, é a forte afinidade com o ASP.NET, mais especificamente, com o assembly System.Web.dll, o que fica difícil de reutilizar a mesma em outras aplicações que não sejam web. Caching é um recurso útil para qualquer tipo de aplicação ou serviço, ou seja, qualquer um pode fazer uso deste recurso afim de melhorar a performance.

Sabendo desta dificuldade, o time de Patterns & Practices da Microsoft, decidiu criar um framework para atender esta demanda. Este framework foi chamado de Caching Application Block, e que foi incorporado ao Enterprise Library. Como isso é uma necessidade de grande parte das aplicações, e para alinhar com outros produtos da Microsoft, ela decidiu incorporar este recurso no .NET Framework, e a partir da versão 4.0 do mesmo, já há um assembly chamado System.Runtime.Caching.dll, que fornece os tipos necessários para permitir que nossas aplicações façam uso de estrutura de caching sem a necessidade de recorrer à componentes extras. A ideia deste artigo é explorar esse assembly e os principais tipos que ele fornece.

O primeiro tipo que vamos analisar neste assembly é o ObjectCache. Esta classe representa o caching em si, armazenando aqueles objetos que desejamos manter no cache. Esta classe é abstrata, e serve como base para criar qualquer nova implementação de cache em memória, fornece métodos comuns para qualquer estrutura de caching, tais como: Add, Get e Remove. Através da imagem abaixo, podemos perceber que esta classe implementa a interface IEnumerable<KeyValuePair<string, object>>, para armazenar e iterar pelos elementos que estarão contidos no cache.

Na imagem acima podemos visualizar um tipo chamado MemoryCache, que herda diretamente da classe ObjectCache. Essa classe é uma implementação para caching em memória, bem próxima aquela que utilizamos com o ASP.NET (System.Web.Caching.Cache), mas como sabemos, com o benefício de ser utilizado por qualquer tipo de aplicação.

O primeiro passo para trabalhar com o caching, é justamente criar a instância do “container”, que servirá como repositório, em memória, para todos os elementos que desejamos colocar em caching. Para o exemplo, vamos utilizar a implementação que existe no .NET Framework, que é a MemoryCache. Essa classe já fornece uma propriedade estática, chamada Default. Essa propriedade tem a finalidade de retornar sempre uma instância padrão da mesma, que para grande partes dos cenários, somente uma instância dela é usada por toda a aplicação.

A criação da instância da classe MemoryCache é criada quando se acessa a propriedade Default pela primeira vez, e a partir daí, compartilha a mesma instância com toda a aplicação, desde que ela seja sempre acessada a partir desta propriedade. Você pode criar uma nova instância da classe MemoryCache ao invés de utilizar a propriedade Default, mas você terá que saber como lidar e acessar essa nova instância na aplicação. Se optar por instanciar diretamente, você deve informar uma string contendo um nome que você dará à ela, que será utilizado pelo sistema de configuração, que será comentado mais tarde, ainda neste artigo.

Depois disso, o uso desta API é bastante trivial, bem próximo ao que já conhecemos quando manipulamos qualquer estrutura de caching. O método Add possui algumas versões (sobrecargas), onde cada uma delas permite informar um conjunto diferente de parâmetros, mas com um único propósito, o de acomodar corretamente o item dentro do caching. Uma das versões do método recebe um tipo chamado CacheItem. O CacheItem representa um elemento individual dentro do cache, que recebe duas principais informações: chave e valor, que são autoexplicativas. É importante informar que o valor sempre é do tipo System.Object, o que nos obrigará a trabalhar com conversões durante a extração do elemento do cache. O código abaixo é um exemplo de como adicionar um dado dentro do caching:

MemoryCache cache = MemoryCache.Default;

if (!cache.Contains(“chave”))
    cache.Add(new CacheItem(“chave”, DateTime.Now), null);

O método Add retorna um valor boleano indicando se o item foi ou não adicionado com sucesso. Caso a chave já exista dentro do cache, esse método retornará False. Já para a extração do item de cache, basta recorrer ao indexer passando o nome da chave, ou ainda, utilizando LINQ, já que o caching implementa a interface IEnumerable<T>.

//Via Indexer
Console.WriteLine(cache[“chave”]);

//Via LINQ
var itens =
    (from x in cache where x.Key == “chave” select x.Value).OfType<DateTime>();

Políticas de Caching

Uma das preocupações ao adicionar um item no cache, é o período de tempo em que ele deverá permanecer por lá. Isso é importante para garantir que a informação não permaneça lá por um tempo em que a torne inválida, devolvendo para os seus clientes dados que já estão defasados.

Com a finalidade de controlar a periodicidade do item no cache, temos uma classe chamada CacheItemPolicy. Cada instância desta classe estará associada com um item no cache, e deve ser informada no momento da adição do item no cache. Entre as principais propriedades desta classe, temos AbsoluteExpiration e SlidingExpiration. A primeira determina um DateTime que representa uma data precisa para expiração do item; já a SlidingExpiration determina um TimeSpan que irá renovar o item no cache se ele for acessado dentro daquele período de tempo, caso contrário, será expirado.

Há também uma propriedade chamada Priority, que como o próprio nome diz, determina a prioridade do item no caching, caso a memória seja necessária por outro recurso, o item será descartado levando em consideração a prioridade dele. Há ainda duas propriedades que determinam callbacks, e que são utilizados para interceptar alguns pontos do runtime. A propriedade RemovedCallback recebe o ponteiro para um método que segue a assinatura imposta pelo delegate CacheEntryRemovedCallback, e será invocado depois que o item for removido do cache. Isso permite você criar alguma regra para recolocar o item no cache, para evitar que você faça isso somente quando o item for novamente exigido. Abaixo temos um exemplo da utilização deste callback:

cache.Add(new CacheItem(“chave”, DateTime.Now), new CacheItemPolicy()
{
    RemovedCallback = args => RecarregarInformacao(args),
    AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(2)
});

Há uma restrição com relação a quantidade de memória total que pode ser utilizada pelo MemoryCache. O limite pode/deve ser especificado através da propriedade CacheMemoryLimit, que recebe um valor (representado por um Int64), que corresponde em megabytes, a quantidade máxima que poderá ser alocada para os itens do cache. O valor padrão (zero) permite ao próprio cache, controlar a quantidade de memória disponível pelo computador onde a aplicação está rodando.

Monitores

Nem sempre a expiração através de um período de tempo (renovável ou não) é o suficiente para determinar a remoção do item do cache. As vezes podemos nos dar o luxo de esperar até que um outro elemento seja alterado para notificar que o item do cache deve ser removido. Quando dimensionamos o tempo de expiração ele pode ser curto ou longo demais, e isso pode acarretar em problemas na visualização dos dados que lá estão.

Um exemplo clássico é manter o cache de um result-set proveniente de uma tabela do banco de dados, e manter o cache até que alguma ação (inserção, atualização ou exclusão) que justifique a mudança seja executada naquela tabela. Aqui estamos dependendo de um tabela do banco de dados. Podemos nos basear em um arquivo, que qualquer mudança nele, resulte na remoção do item do cache.

Para atender essa necessidade, a Microsoft incluiu neste assembly os monitores. Alguns monitores já foram criados por ela, e disponibilizados para usá-los em nossas aplicações. A classe abstrata ChangeMonitor é a base para qualquer monitor, fornecendo toda a estrutura necessária para que as classes derivadas possam detectar a mudança em algum recurso e, consequentemente, notificar que isso ocorreu, permitindo ao monitor notificar a nossa aplicação (cache). A imagem abaixo ilustra a hierarquia dos monitores do .NET Framework:

O SqlChangeMonitor recebe em seu construtor a instância da classe SqlDependency, que reportará qualquer alteração em uma tabela específica e, consequentemente, passará a informação para o monitor de cache, que por sua vez, removerá o item associado ao mesmo. Já o monitor CacheEntryChangeMonitor serve para monitorar as entradas no cache, e é usado quando o cache precisa monitorar seus próprios itens. FileChangeMonitor é a classe base para monitorar o sistema de arquivos. A implementação nativa para esta classe, é a classe HostFileChangeMonitor, que irá monitorar algumas ações que eventualmente aconteçam em um arquivo ou diretório, e irá repassar para o cache. Internamente esta classe recorre ao tradicional FileSystemWatcher. O exemplo abaixo ilustra a sua utilização:

CacheItemPolicy policy = new CacheItemPolicy();
policy.ChangeMonitors.Add(new HostFileChangeMonitor(new[] { @”C:TempTeste.txt” }));
cache.Add(new CacheItem(“chave”, DateTime.Now), policy);

O construtor da classe HostFileChangeMonitor recebe uma coleção de paths para arquivos e/ou diretórios que deseja monitorar. Depois de instanciado e devidamente configurado, ele deverá ser adicionado na coleção de monitores, exposta pela classe CacheItemPolicy, através da propriedade ChangeMonitors, onde um item no cache poderá depender de qualquer alteração que ocorra em algum dos monitores associados à ela.

Configuração

Algumas poucas configurações do cache, estão disponíveis para serem alteradas através do arquivo de configuração da aplicação. Isso permite que adequarmos o runtime mesmo depois da aplicação instalada, sem a necessidade de recompilar a mesma. Há uma seção chamada <system.runtime.caching />, que podemos configurar os caches que nossa aplicação utiliza.

O vínculo entre o cache criado pela aplicação e o arquivo de configuração, se dá pelo nome do mesmo. Quando utilizamos a propriedade estática Default da classe MemoryCache, a instância da mesma é criada, e o nome dela é definido como “Default”. Quando decidimos criarmos mais do que uma instância da classe MemoryCache, somos obrigados a utilizar o construtor público, que exige uma string representando o nome do cache. Esse nome poderá ser utilizado para se relacionar com o arquivo de configuração.

O exemplo abaixo permite a configuração do cache padrão. Estamos configurando informações pertinentes à memória, como já foi falado acima. Ainda temos uma outra configuração (que também é possível através do modelo imperativo), que define o intervalo (PollingInterval) em que o cache compara o que temos armazenado com o limite especificado, para evitar o constante acesso ao mesmo.

<?xml version=”1.0″?>
<configuration>
  <system.runtime.caching>
    <memoryCache>
      <namedCaches>
        <add name=”Default”
             cacheMemoryLimitMegabytes=”1024″
             pollingInterval=”00:05:00″ />
      </namedCaches>
    </memoryCache>
  </system.runtime.caching>
</configuration>

Contadores de Performance

Finalmente, para monitorar a saúde do caching que está sendo utilizado pelo aplicação, podemos recorrer à alguns contadores de performance que são instalados pelo próprio .NET Framework, que tem a finalidade de coletar e exibir informações que retratam a situação atual do caching. Para visualizar, podemos recorrer à ferramenta Performance Monitor do Windows, e procurar pela categoria .NET Memory Cache 4.0. Lá você terá uma instância para cada aplicação que utiliza este recurso.

Conclusão: No decorrer deste artigo, pudemos analisar este novo assembly que traz uma porção de tipos para agregarmos a nossa aplicação/componente recursos de caching, algo que antes era somente possível se utilizando de um componente a parte, quando não aquele fornecido para trabalhar exclusivamente com aplicações ASP.NET.

Publicidade

7 comentários sobre “Caching do .NET Framework

    • Olá Israel. Primeiramente, parabéns pelo artigo. Gostaria de saber se para aplicações Asp.Net WebForms (frmk4.0) o indicado é continuar utilizando o System.Web.Caching ou centralizar as operações de cache no System.Runtime.Caching.
      []

    • Israel, boa tarde.
      Esse esquema de providers existe apenas para o OutputCache ou também para cache de dados ? Dei uma olhada em seu artigo e na biblioteca do Msdn, mas só encontrei esse esquema para para o OutputCache.

    • Boas Alessandro,

      Se você está se referindo ao Object Cache do ASP.NET (HttpContext.Current.Cache), então ele não faz uso deste esquema, ou seja, você pode delegar ao cache do .NET apenas o que se refere ao OutputCache, que incluiu na versão 4 a possibilidade de utilizar providers.

      Caso você desenhe o sistema com estensibilidade, então você poderia abstrair o ObjectCache em uma estrutura próprio, podendo, mais tarde, escolher entre o cache do ASP.NET, do .NET, Velocity, etc., mas também acho que seria mais interessante se isso fosse nativo.

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s