Desenvolvimento - ASP. NET

Utilizando o Cache do ASP.Net para melhorar o desempenho das páginas

Nesse artigo iremos mostrar como usar o cache para armazenar um dataset na memória, e mostrar esses dados em um datagrid.

por Rodrigo Vieira



A plataforma ASP.Net trouxe como novidade uma biblioteca muito interessante para "caching", ou seja, armazenar dados na memória do servidor de forma inteligente.

Nesse artigo iremos mostrar como usar o cache para armazenar um dataset na memória, e mostrar esses dados em um datagrid. Para tanto, utilizaremos um arquivo XML como fonte dos dados, por questão de simplicidade, mas o mesmo código poderia ser usado para bancos de dados, onde os ganhos de desempenho seriam ainda maiores.

Parte 1 - Mostrando os dados XML em um datagrid

Primeiramente, vamos escrever o código básico, sem cache, simplesmente buscando os dados em um arquivo XML, carregando-os em um Dataset e ligando este a um Datagrid.

O arquivo XML a ser usado, "dados.xml", tem a seguinte forma:

O arquivo XML utilizado no nosso exemplo possui cerca de 750 títulos, para que o carregamento dos dados demore um pouco e possamos ver a vantagem de utilizarmos o cache.

Ao formulário só precisamos por enquanto adicionar um datagrid, que deixaremos com o nome original, "datagrid1", e a opção "auto-generate columns" marcada com "True".

O código para carregar esses dados é assim:

Private Sub Page_Load(…) Handles MyBase.Load
   If Not Page.IsPostBack Then
      CarregaDados()
   End If
End Sub

Sub CarregaDados()
   "carrega o arquivo XML em um dataset
   Dim ds As DataSet
   ds = New DataSet
   ds.ReadXml(Server.MapPath("dados.xml"), _
              XmlReadMode.InferSchema)

   "liga os dados ao datagrid
   DataGrid1.DataSource = ds
   DataGrid1.DataBind()
End Sub

E, ao ser executado, mostraria o datagrid com cerca de 750 linhas:

Agora que sabemos o básico sobre como utilizar XML com Datasets e Datagrids, vamos ao que interessa!

Parte 2 - Criando um cache simples

A biblioteca de cache do ASP.net é bem flexível e possui vários detalhes interessantes, tais como dependências de chave e arquivo e funções de Callback para itens removidos, mas pra começar vamos criar um cache descomplicado, para entendermos a "filosofia" por trás da técnica.

A idéia básica para utilizarmos o cache é a seguinte:

  • O programa busca o dataset do cache, que poderá conter dados (caso o cache já tenha sido carregado) ou não;
  • Se o dataset vier vazio, ou seja, o cache não foi carregado ainda (por exemplo, na primeira execução do programa), os dados serão buscados no arquivo XML, armazenados no cache e ligados ao datagrid:
  • Se o dataset vier preenchido do cache, basta ligar o mesmo ao datagrid diretamente.

Também adicionaremos 2 botões e 2 labels no topo do formulário: um label chamado "lblFonte" para informar se os dados que estamos mostrando no momento vieram do cache ou do arquivo XML, um label "lblTempoGasto" pra podermos comparar quanto tempo leva cada caso, e dois botões, "cmdRecarregarPagina" e "cmdLimparCache".

Vale lembrar que o cache funciona da mesma forma que um dicionário e um objeto de sessão, ou seja, acessamos os itens a partir de uma "chave", que retorna um objeto, ou seja, o código

Obj = cache("Item1")

Retornará o objeto armazenado no cache identificado pela chave "Item1".

O código, atualizado, fica assim:

Sub CarregaDados()
   Dim startTime As DateTime = Now
   Dim ds As DataSet

   "busca o dataset no cache
   ds = Me.Cache("LivrosDS")

   "se nao tenho esse dataset no cache, 
   "carrego do arquivo XML e coloco no cache
   If ds Is Nothing Then
      ds = New DataSet
      ds.ReadXml(Server.MapPath("dados.xml"), _ 
                 XmlReadMode.InferSchema)

      Me.Cache.Insert("LivrosDS", ds)
      lblFonte.Text = "Arquivo XML"
   Else
      lblFonte.Text = "Cache"
   End If

   "liga os dados ao datagrid
   DataGrid1.DataSource = ds
   DataGrid1.DataBind()

  "mostra o tempo gasto
  lblTempoGasto.Text = Now.Millisecond - startTime.Millisecond & " msec"
End Sub

Private Sub cmdRecarregarPagina_Click(…) Handles cmdRecarregarPagina.Click
   CarregaDados()
End Sub

Private Sub cmdLimparCache_Click(…) Handles cmdLimparCache.Click
   Me.Cache.Remove("LivrosDS")
   CarregaDados()
End Sub
Agora podemos executar a página, e ver o que acontece:

Repare que, como é a primeira vez que a página é executada, os dados foram buscados do arquivo XML (conforme mostrado no primeiro label) e a operação levou 172 milissegundos.

Ao pressionarmos o botão "Recarregar página", temos o seguinte resultado:

Agora os dados foram carregados do cache, e o tempo de carregamento da página caiu para 15 milissegundos, 11 vezes mais rápido, nada mal! Em aplicações que utilizam bancos de dados, os ganhos podem ser ainda maiores.

Parte 3 - Aperfeiçoando o cache com o uso de dependências

Como vimos, utilizar a API de cache do ASP.Net pode nos ajudar em muito a melhorar o tempo de resposta das páginas, mas como tudo na vida, isso também tem um custo. Não podemos simplesmente carregar milhares de objetos na memória e deixá-los eternamente lá, pois isso sobrecarregaria o servidor.

Outro ponto a ser lembrado é que, com o código acima, se mudássemos os dados no arquivo XML os usuários continuariam vendo dados antigos até que o objeto fosse eventualmente retirado do cache. Em alguns sistemas onde os dados são atualizados com freqüência isso poderia ser um problema.

Dependendo do contexto em que você pretende usar o cache, existem 3 alternativas para manter o cache atualizado e reduzir o uso de memória:

  1. Podemos associar uma dependência de arquivo para o objeto, ou seja, toda vez que um determinado arquivo (no nosso caso, dados.xml) for alterado, o elemento deverá ser automaticamente retirado do cache;
  2. Podemos definir um limite de tempo absoluto para o objeto, ou seja, dizer que depois de X minutos o mesmo deverá ser retirado do cache;
  3. Podemos definir um limite de tempo por desuso para o objeto, ou seja, dizer que o mesmo deverá ser retirado do cache caso não seja acessado por X minutos.

Provavelmente a primeira alternativa é a ideal para o nosso exemplo, mas vamos ver o código para os três. Se dermos uma olhada na função cache.Insert, veremos que a mesma possui a seguinte assinatura:

Cache.Insert(key as string, value as object, dependencies as CacheDependency, absoluteExpiration as date, slidingExpiration as TimeSpan)

Os dois primeiros parâmetros já conhecemos, é a chave e o valor do objeto a ser armazenado no cache. Os outros 3 parâmetros equivalem, respectivamente, às 3 alternativas mencionadas.

- Implementando dependência em arquivo

Para implementar a dependência de arquivo mencionada em a), basta alterar o código da seguinte forma:

Me.Cache.Insert("LivrosDS", ds, _ 
New Caching.CacheDependency(Server.MapPath("dados.xml")))

Para testar, basta rodar o programa e recarregar a página. Conforme esperado, na segunda vez os dados serão carregados do cache. Agora abra o arquivo XML e altere algum dos dados, e salve. Recarregue a página e você vai ver que dessa vez os dados foram carregados do arquivo de novo!

Com isso já temos uma solução muito inteligente, que garante que o usuário terá tempos de resposta mínimos mas ao mesmo tempo sempre verá dados atualizados.

- Implementando limpeza por tempo absoluto

Para implementar a solução b), basta alterar o código da seguinte forma:

Me.Cache.Insert("LivrosDS", ds, Nothing, Now.AddMinutes(5), Caching.Cache.NoSlidingExpiration)

O parâmetro "absoluteExpiration" recebe uma data-hora em que o elemento deverá ser retirado do cache arbitrariamente. No nosso caso, ao usarmos Now.AddMinutes(5) informamos ao programa que o mesmo deverá acontecer em 5 minutos.

- Implementando limpeza por desuso

A solução c), por outro lado, recebe como parâmetro um objeto do tipo TimeSpan, ou seja, um intervalo de tempo. Caso quiséssemos que o dataset fosse retirado da memória caso não seja acessado por 3 minutos, entraríamos o seguinte código:

Me.Cache.Insert("LivrosDS", ds, Nothing, _
Caching.Cache.NoAbsoluteExpiration, New TimeSpan(0, 3, 0))

Parte 4 - Dependência em bancos de dados?

Na parte 3 vimos que é fácil criar uma dependência em arquivo, para que o cache monitore o acesso a um ou mais arquivos e remova elementos quando houverem alterações nos dados.

Como você deve estar pensando agora, o problema é que em geral não buscamos dados de arquivos XML ou texto, e sim bancos de dados. Na versão 1.1 do framework .Net não é possível criar dependência em banco de dados, o que deverá ser incluído na versão 2.0 em 2005.

Enquanto a nova versão não vem, podemos utilizar um pequeno macete para criar tal dependência, ainda que não seja muito prático: basta criar "triggers"a serem executadas após operações de Insert, Delete e Update do banco de dados, e em tais triggers alterar arquivos-texto no servidor, e então criar dependëncias de arquivo como de costume. Não é uma solução muito elegante, mas vem sendo amplamente utilizada (aparentemente até pela Microsoft, que publicou tal dica no site MSDN) e resolve o problema.

A API de cache, utilizada em conjunto com uma arquitetura em 3 ou mais camadas, pode tornar a sua aplicação muito rápida, e portanto é uma ferramenta que deve ser seriamente considerada, principalmente em sites de grande tráfego.

Abracos, e boa sorte!

Rodrigo Vieira

Rodrigo Vieira - MCSD e MCAD, formado em Ciência da Computacão, trabalhando há 5 anos em uma empresa de telecomunicacões em Oslo, Noruega, desenvolvendo aplicativos para Intranet nas plataformas .Net e Oracle. Entusiasta de Python, Mono, Linux e software livre em geral.
Blog The Spoke:
http://br.thespoke.net/MyBlog/rodviking/MyBlog.aspx