Async Handler

Desde a versão 1.x do ASP.NET podemos facilmente criar um handler para permitir a execução de algum código. O idéia por detrás deste handler, é justamente servir como alvo para alguma requisição. Um exemplo muito típico é a extração de arquivos do banco de dados, arquivos do disco, geração automática de imagens, etc..

Havia alguns detalhes necessários na versão 1.x que, se não realizados, não funcionava adequadamente. A partir da versão 2.0, a Microsoft incluiu um novo tipo de arquivo, com extensão ASHX, chamado de Generic Handler. Esse arquivo traz uma classe que, por padrão, implementa a interface IHttpHandler, cabendo aos desenvolvedores implementar o método ProcessRequest para tratar o pedido.

O problema é que o processamento deste handler estará ocupando uma thread do ThreadPool que, por sua vez, serve as requisições para as páginas da aplicação. Caso o código a ser realizado dentro deste handler for um processo custoso, podemos comprometer as outras requisições que nada tem a ver com ele (este artigo explica mais detalhes sobre este comportamento).

Para permitir que o handler também faça o uso do processamento assíncrono, podemos fazer com que o mesmo implemente a interface IHttpAsyncHandler e, ao invés de implementar o método ProcessRequest, devemos implementar os métodos BeginProcessRequest e EndProcessRequest. O exemplo abaixo utiliza os métodos (BeginRead e EndRead) para leitura assíncrona de um arquivo do disco:

public class ImageAsyncHandler : IHttpAsyncHandler
{
    private struct InnerState : IDisposable
    {
        public HttpContext HttpContext;
        public FileStream Stream;

        public void Dispose()
        {
            if (this.Stream != null)
                this.Stream.Dispose();
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }

    public IAsyncResult BeginProcessRequest(HttpContext context,
        AsyncCallback cb, object extraData)
    {
        string fileName =
            context.Server.MapPath(context.Request.QueryString[“FileName”]);

        FileStream fs = new FileStream(fileName, FileMode.Open);
        int length = (int)fs.Length;

        return fs.BeginRead(new byte[length], 0, length, cb,
            new InnerState()
            {
                HttpContext = context
                , Stream = fs
            });
    }

    public void ProcessRequest(HttpContext context) { }

    public void EndProcessRequest(IAsyncResult result)
    {
        using (InnerState state = (InnerState)result.AsyncState)
        {
            int length = state.Stream.EndRead(result);
            state.Stream.Position = 0;
           
            byte[] temp = new byte[length];
            state.Stream.Read(temp, 0, length);

            state.HttpContext.Response.OutputStream.Write(temp, 0, length);
        }
    }
}

Esse processo não mudará nada o resultado final do handler, mas trabalhará de uma forma muito mais otimizada, garantido que threads criadas para servir a aplicação não fiquem ocupadas executando um processamento mais custoso. Caso a imagem ou os dados venham de um banco de dados, podemos combinar essa técnica com os métodos assíncronos do ADO.NET 2.0.