Durante o desenvolvimento de soluções para a Web (como sites em ASP.NET ou serviços WCF) pode ser necessário, em algumas situações bem específicas, evitar o acesso simultâneo a um determinado recurso. Tomando por base tal aspecto, um equívoco bastante comum de muitos programadores é ignorar o fato de que uma aplicação Web difere em muito de softwares para desktop convencionais (como projetos criados em Windows Forms ou WPF, por exemplo).

Um sistema construído em ASP.NET ou WCF estará, normalmente, hospedado em um computador com o sistema operacional Windows e no qual foi instalado o IIS (Internet Information Services). Já o IIS é um serviço que atua como um servidor de aplicação, ou seja, compete a este software controlar os recursos necessários para a execução de sites e Web Services que estão sob a responsabilidade do mesmo; isto envolve o gerenciamento de memória, o recebimento e o tratamento de requisições recebidas via protocolo HTTP, dentre outras atividades.

Existirá geralmente em um servidor IIS uma instância de uma aplicação (site ou Web Service), com esta última sendo responsável pelo tratamento em paralelo de múltiplas requisições geradas pela ação de usuários ou, até mesmo, por outros sistemas. É neste ponto que problemas poderão acontecer.

Bancos de dados relacionais mais sofisticados como SQL Server e Oracle já contam nativamente com mecanismos capazes de comportar o processamento simultâneo de diversas operações. Não é este o caso se for considerada a persistência de informações num arquivo-texto que funcione como um log de erros: falhas no processamento de diversas requisições podem acontecer num curto intervalo de tempo, levando a conflitos em operações de escrita sobre este arquivo e, provavelmente, perdas ou gravação incorreta de informações.

Sobre este último cenário mencionado, por mais que este tipo de prática envolvendo registros de log esteja normalmente associado ao uso de uma tabela num banco relacional, a adoção desta alternativa se faz necessária quando não se conseguir o acesso a uma base. Motivos para isto podem ser uma indisponibilidade do servidor de bancos de dados, problemas na própria base que impossibilitam a leitura/gravação na mesma etc.

Embora a necessidade de impedir múltiplas tentativas de acesso a um recurso seja uma demanda mais frequente no ambiente Web, outras aplicações como programas em Windows Forms ou WPF podem vir a se valer de técnicas que se baseiam nesta característica. Em se tratando da plataforma .NET, um dos meios possíveis de se implementar isto é configurando um método de uma classe para que o mesmo seja acionado de forma sincronizada.

O conceito de sincronização, dentro deste contexto, deve ser compreendido como a capacidade de apenas uma thread conseguir executar um método num determinado instante. Isto é particularmente útil no caso de sites e Web Services, haja visto que uma única instância da aplicação é responsável por processar solicitações de múltiplas threads (originadas através de requisições de usuários ou outros sistemas).

A implementação deste tipo de comportamento a partir do .NET é feita por meio da utilização do atributo MethodImplAttribute (pertencente ao namespace System.Runtime.CompilerServices), em conjunto com o valor de enumeration MethodImplOptions.Synchronized que é passado como parâmetro ao mesmo. Ao associar tal atributo a um método, o runtime do .NET Framework entenderá que a operação em questão poderá ser executada por apenas uma thread num determinado instante. Outras threads que dependam daquela funcionalidade serão forçadas a esperar, competindo ao .NET Framework determinar a próxima requisição a ser atendida.

Na Listagem 1 é apresentando um exemplo de classe que emprega este mecanismo com o intuito de sincronizar a utilização de um método. O método RegistrarExcecao da classe estática LogHelper recebe como parâmetro um objeto do tipo Exception; na sequência é gerada uma instância do tipo StreamWriter, de forma que se gere um novo arquivo ou se adicione conteúdo a este se o mesmo já existir (o segundo parâmetro com o valor “true” é responsável por fazer com que isso aconteça desta forma).

Uma observação importante a se fazer é quanto ao uso de atributos em .NET: por convenção, todas as classes que implementam este mecanismo têm seu nome terminado pelo sufixo “Attribute”. No entanto, ao se vincular este elemento a uma construção de código, este sufixo acaba por ser omitido. Logo, ao invés de se utilizar “MethodImplAttribute”, o atributo foi associado ao método RegistrarExcecao como “MethodImpl”.

Conforme pode ser observado ainda, serão persistidas no arquivo informações como a data do erro (obtendo-se para isto a data atual do sistema), o tipo da exceção gerada (acionando o método GetType e em seguida a propriedade Name da instância correspondente), a mensagem associada a esta (propriedade Message), bem como o relação dos diversos métodos que foram acionados até que o erro acontecesse (propriedade StackTrace).

Listagem 1: Utilizando o atributo MethodImplAttribute na definição de um método


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Runtime.CompilerServices;

namespace TesteSincronizacao
{
    public static class LogHelper
    {
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static void RegistrarExcecao(Exception ex)
        {
            using (StreamWriter writer = new StreamWriter(
                @"C:\Testes\LogErros.txt", true))
            {
                writer.WriteLine("Data/Hora do Erro: " +
                    DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"));
                writer.WriteLine("Tipo da Exceção: " +
                    ex.GetType().Name);
                writer.WriteLine("Mensagem: " + ex.Message);
                writer.WriteLine("Stack Trace: " + ex.StackTrace);
                writer.WriteLine("------------------------------");
            }
        }
    }
}

Um uso hipotético para a operação estático RegistrarExcecao poderia ser na implementação do evento Application_Error, dentro do arquivo Global.asax de uma aplicação Web. No caso, o método Application_Error será executado em todas as ocasiões nas quais uma exceção ocorrer em um ponto do sistema e o erro em questão não for devidamente tratado. Muitas vezes, este tipo de ocorrência é registrado em uma tabela de banco de dados; contudo, se o acesso à base em questão estiver indisponível, a opção de gravar o erro em um arquivo-texto para posterior análise pode representar a única alternativa viável.

A Listagem 2 demonstra um exemplo disto: através do objeto Server é acionada a operação GetLastError e, em seguida, GetBaseException; com isto, é possível obter a exceção que desencadeou todo um problema, com esta última servindo de base para a gravação de informações no arquivo de log. Já a Figura 1 exibe o conteúdo do arquivo gerado, com alguns erros já registrados no mesmo.

Listagem 2: Evento Application_Error acessando um método sincronizado


void Application_Error(object sender, EventArgs e)
{

    ...

    LogHelper.RegistrarExcecao(
        Server.GetLastError().GetBaseException());

    ...
}
Exemplo de log gerado através do método RegistrarExcecao

Figura 1: Exemplo de log gerado através do método RegistrarExcecao

Por mais que o mecanismo de sincronização de métodos aqui exposto seja extremamente simples do ponto de vista de codificação, esta técnica é bastante poderosa, podendo ser de grande valia em situações que exijam o tratamento de diversas solicitações ao mesmo tempo. Espero que o conteúdo deste artigo possa ter sido útil. Até uma próxima oportunidade!