ADO.NET – Trabalho Assíncrono


Temos nas versões 1.x do ADO.NET três métodos bastante importantes dentro da classe XXXCommand, sendo eles: ExecuteReader (retorna um result-set de dados), ExecuteXmlReader (retorna um result-set em formato XML (XmlReader)) e ExecuteNonQuery (executa uma Transact-SQL statement e retorna um número inteiro que representa o número de linhas afetadas). Todos estes métodos são responsáveis por executar de forma síncrona, bloqueando assim a Thread corrente.

O problema deste comportamento é que temos que aguardar o processamento da base de dados finalizar para que possamos novamente ter acesso operante ao sistema. Isso acaba sendo bastante complicado, já que muitas vezes o processo é demorado, principalmente quando as regras de negócio estão dentro da base de dados. Apesar de se conseguir realizar um processo assíncrono no ADO.NET 1.x com o auxílio de Delegates Assíncronos e da classe ThreadPool, a Microsoft decidiu facilitar nesta versão do ADO.NET a criação de comandos assíncronos na base de dados, onde foi adotada uma metodologia similar ao que temos dentro do .NET Framework, mas até o momento é somente suportado pelo provider SqlClient.

Para estes mesmos métodos, temos também mais dois métodos auxiliares (Begin e End) para suportar chamadas assíncronas. A tabela abaixo mostra os novos métodos:

Síncrono Assíncrono (Begin) Assíncrono (End)
ExecuteNonQuery BeginExecuteNonQuery()
BeginExecuteNonQuery(AsyncCallback, Object)
EndExecuteNonQuery(IAsyncResult)
ExecuteReader BeginExecuteReader()
BeginExecuteReader(AsyncCallback, Object)
EndExecuteReader(IAsyncResult)
ExecuteXmlReader BeginExecuteXmlReader()
BeginExecuteXmlReader(AsyncCallback, Object)
EndExecuteXmlReader(IAsyncResult)

Os métodos BeginXXX têm geralmente duas sobrecargas, onde em uma delas não é passado nenhum parâmetro. Já na outra dois parâmetros são requeridos: um delegate do tipo AsyncCallback que apontará para um procedimento de callback, que será executado quando a operação for finalizada; já o segundo parâmetro trata-se de um objeto que é passado para esse procedimento de callback, que poderá ser recuperado pela propriedade AsyncState. Essa propriedade é acessada através do objeto IAsyncResult (retornado pelo método BeginXXX), o qual é passado como parâmetro para o procedimento de callback e, está definido na assinatura do delegate AsyncCallback.

Como já foi dito, esses métodos retornam um objeto do tipo IAsyncResult que representa o status da operação assíncrona. Este, por sua vez, é passado para o procedimento de callback (quando existir), que provê informações importantes e também necessárias para finalizarmos o processo assíncrono. Já os métodos EndXXX devem ser invocados para completar a operação do processo assíncrono. Em seu parâmetro devemos também passar um objeto do tipo IAsyncResult, que será retornado pelo método BeginXXX.

Antes de visualizarmos os exemplos concretos desta funcionalidade, temos uma configuração a ser realizada na ConnectionString que devemos nos atentar para que as operações resultem. Nesta string de conexão com a base de dados, necessitamos definir um parâmetro que indicará ao .NET que esse banco de dados poderá trabalhar com processos assíncronos. Trata-se do parâmetro Asynchronous Processing que, se quisermos em algum momento da aplicação trabalhar de forma assíncrona, devemos definí-la como True. O código abaixo exemplifica como deverá ficar a string de conexão para suportar esta funcionalidade:

"integrated security=SSPI;data source=localhost;
    initial catalog=Northwind;Asynchronous Processing=True"

Já ara exemplificar toda a teoria até o momento, veremos os dois exemplos de operações assíncronas na base de dados, onde, em um dos casos, somente vamos lançar a operação assíncrona para ser realizada. Já no segundo exemplo, utilizaremos uma função de callback. É importante dizer que para simularmos uma operação demorada na base de dados SQL Server, utilizaremos o comando WAITFOR DELAY que, especificado um tempo, ele aguardará pelo mesmo até executar o procedimento que queremos que a base de dados faça. Nos exemplos abaixo estaremos passando para esse comando o valor de 10 segundos. Primeiramente analisaremos a forma de lançar um processo assíncrono na base de dados sem a utilização de callbacks:

using System.Data.SqlClient;
using System.Configuration;

namespace WindowsApplication2
{
    public partial class Form1 : Form
    {
        private SqlCommand _cmd;
        private SqlConnection _conn;
        private IAsyncResult _result;

        private void IniciaProcesso_Click(object sender, EventArgs e) {
            try {
                this._conn = 
                    new SqlConnection(GetConnStringFromConfigFile("ConnString"));
                this._cmd = 
                    new SqlCommand("WAITFOR DELAY '0:0:10'; " + 
                        "INSERT INTO Pedido (Cliente) VALUES ('Israel Aece')", this._conn);

                this._conn.Open();
                this._result = this._cmd.BeginExecuteNonQuery();
            }
            catch {
                this.TextBox1.Text = "Banco de dados indisponível.";
            }
        }

        private void VerificaProcesso_Click(object sender, EventArgs e) {
            try {
                if (!this._result.IsCompleted) {
                    this.TextBox1.Text = "Incompleto.";
                }
                else {
                    this.TextBox1.Text = "Finalizado. " +
                        this._cmd.EndExecuteNonQuery(this._result).ToString() +
                        " registro(s) afetado(s).";

                    this._conn.Close();
                }
            }
            catch (Exception ex) {
                this.TextBox1.Text = "Erro: " + ex.ToString();
                this._conn.Close();
            }
        }
    }
}

Ao clicar no botão “Iniciar Processo”, enviamos para a base de dados o comando a ser executado através do método BeginExecuteNonQuery do objeto SqlCommand que temos dentro do formulário. Depois de realizado isso, verificamos se o processo foi ou não finalizado através da propriedade IsCompleted do objeto IAsyncResult, a qual já vimos a sua funcionalidade um pouco mais acima. Se o processo não foi finalizado, definimos na caixa de texto a mensagem que ainda está em processamento. Do contrário, exibimos a quantidade de registros afetados pela query.

É importante dizer que até que o processo não for finalizado, a conexão com a base de dados não poderá ser fechada. Se notarmos, o método Close do objeto SqlConnection somente é chamado quando a propriedade IsCompleted retornar True, ou quando uma falha na execução da query for encontrada, pois o bloco Catch será disparado. A imagem abaixo ilustra esse processo que descrevemos acima:

Figura 1 – Verificando se o processo foi finalizado através da propriedade IsCompleted.

Callbacks

Ao contrário do exemplo anterior, com a utilização de callbacks temos a vantagem de executarmos algum código quando o processo assíncrono da base de dados for finalizado. Isso tira o encargo do usuário ficar verificando isso, ou seja, quando invocamos qualquer método BeginXXX, temos uma sobrecarga que aceitará um parâmetro do tipo AsyncCallback, que é um Delegate que apontará para um procedimento que será invocado automaticamente quando o processo finalizar. Iremos utilizar o mesmo cenário do exemplo anterior, só que agora com o auxílio dos callbacks. Vejamos o código já implementado:

using System.Data.SqlClient;
using System.Configuration;

namespace WindowsApplication2
{
    public partial class Form2 : Form
    {
        private SqlConnection _conn;

        private void IniciaProcesso_Click(object sender, EventArgs e) {
            try
            {
                this._conn = 
                    new SqlConnection(GetConnStringFromConfigFile("ConnString"));
                SqlCommand cmd =
                    new SqlCommand("WAITFOR DELAY '0:0:10'; " +
                        "INSERT INTO Pedido (Cliente) VALUES ('Israel Aece')", this._conn);

                this._conn.Open();
                AsyncCallback callback = new AsyncCallback(MeuCallback);
                cmd.BeginExecuteNonQuery(callback, cmd);
                this.TextBox1.Text = "Processando...";
            }
            catch {
                this.TextBox1.Text = "Banco de dados indisponível.";
            }
        }

        private void MeuCallback(IAsyncResult result) {
            SqlCommand cmd = (SqlCommand)result.AsyncState;
            this.TextBox1.Text = "Registros afetados: " +
                cmd.EndExecuteNonQuery(result).ToString();

            if (this._conn != null) this._conn.Close();
        }
    }
}

Analisando o código acima, criamos um objeto do tipo SqlConnection e, no evento Click do botão “IniciaProcesso”, atribuímos à instância ao mesmo, recuperando a ConnectionString do arquivo de configuração. Logo em seguida, criamos o objeto SqlCommand, o qual será responsável por criar o comando que será executado na base de dados e, por fim, criamos um delegate do tipo AsyncCallback, onde definimos o procedimento que será executado quando o processo na base de dados for finalizado. Para que o processo seja inicializado, invocamos o método BeginExecuteNonQuery, passando o delegate e o objeto que iremos utilizar no procedimento apontado pelo delegate. Neste caso, iremos mandar para o procedimento o próprio objeto SqlCommand, já que o utilizaremos para encerrar o processo.

Já no procedimento “MeuCallback”, o qual foi definido no delegate para ser processado quando o processo assíncrono for finalizado, reparem que, como parâmetro, recebemos um objeto do tipo IAsyncResult (isso é uma “exigência”, pois o delegate tem essa assinatura), e este por sua vez, fornece uma propriedade chamada AsyncState, a qual retorna um objeto definido pelo usuário, contendo informações sobre a operação assíncrona. Agora, simplesmente fazemos a conversão para o objeto SqlCommand e invocamos o método EndExecuteNonQuery para finalizar o processo assíncrono. É importante que até o retorno deste processo a conexão com a base de dados não pode ser fechada, ou seja, isso somente deverá ser feito quando o processo assíncrono for finalizado.

Figura 2 – Utilizando Callbacks.

Processo Assíncrono e ASP.NET

ASP.NET 2.0 já traz instrinsicamente uma infra-estrutura para trabalharmos com páginas/chamadas assíncronas, simplificando bastante a sua utilização. Isso pode ser usado em conjunto com esta nova funcionalidade de executar assincronamente queries na base de dados. É bastante comum em páginas ASP.NET acessarmos o conteúdo de uma determinada base de dados; sendo assim, podemos efetuar uma query assíncrona dentro de uma base de dados qualquer e retornar ao usuário o result-set e, conseqüentemente, popular um controle do tipo GridView. Devemos, neste caso, criar e codificar o evento PreRenderComplete, que será disparado imediatamente depois do processo assíncrono finalizado, mas antes da página ser renderizada, justamente para termos acesso ao controle GridView e aos demais itens da página. Veremos abaixo o código necessário para a execução de uma query assíncrona dentro de uma página também assíncrona:

using System.Data.SqlClient;

public partial class DB : System.Web.UI.Page
{
    private SqlConnection _conn;
    private SqlCommand _cmd;
    private SqlDataReader _reader;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            this.PreRenderComplete += new EventHandler(Page_PreRenderComplete);

            this.AddOnPreRenderCompleteAsync(
                new BeginEventHandler(IniciaProcesso),
                new EndEventHandler(FinalizaProcesso)
            );
        }
    }

    protected IAsyncResult IniciaProcesso(
        object sender, 
        EventArgs e, 
        AsyncCallback cb, 
        object state)
    {
        this._conn = new SqlConnection(GetConnStringFromConfigFile("ConnString"));
        this._conn.Open();
        this._cmd = new SqlCommand("SELECT * FROM Usuarios", this._conn);
        return this._cmd.BeginExecuteReader(cb, state);
    }

    protected void FinalizaProcesso(IAsyncResult ar)
    {
        this._reader = this._cmd.EndExecuteReader(ar);
    }

    protected void Page_PreRenderComplete(object sender, EventArgs e)
    {
        if (this._reader.HasRows){
            this.GridView1.DataSource = _reader;
            this.GridView1.DataBind();		
        }
    }

    public override void Dispose()
    {
        if (this._conn != null) this._conn.Close();
        base.Dispose();
    }
}

Como as páginas assíncronas, suas funcionalidades e características fogem um pouco do escopo deste artigo, fica aqui uma referência para aqueles que querem entender um pouco mais sobre elas.

ADONET20.zip (118.08 kb)

Publicidade

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