ASP .NET - Criando uma conexão Assíncrona com o SQL Server


Neste artigo vou explicar como criar uma conexão assíncrona com o SQL Server em uma aplicação ASP .NET Web Forms.

Revisão de Conceitos - Teoria

Como você faria se precisasse executar uma consulta ou comando contra um banco de dados SQL Server, de forma assíncrona, ou seja, em segundo plano, enquanto sua aplicação continuasse a realizar outros processamentos ?

Para este serviço você pode usar os métodos BeginExecuteNonQuery, BeginExecuteReader, ou BeginExecuteXmlReader da classe SqlCommand do namespace System.Data.SqlClient para iniciar a operação com o banco de dados como uma tarefa em segundo plano.

Estes métodos retornam um objeto System.IAsyncResult que você pode usar para determinar o estado da operação ou usar uma sincronização via thread para aguardar que a tarefa seja completada.

Para obter o resultado da operação podemos usar o objeto IAsyncResult e os métodos EndExecuteNonQuery, EndExecuteReader, ou EndExecuteXmlReader correspondentes.

Obs: Somente a classe SqlCommand suporte as operações assíncronas que iremos descrever neste artigo. O mesmo não se aplica aos provedores de dados Oracle, SQL Server CE, ODBC, e OLE DB.

Você normalmente irá executar operações em bancos de dados de forma síncrona, pois o seu código vai precisar do resultado da operação antes de continuar. No entanto, às vezes é útil executar uma operação de banco de dados de forma assíncrona, o que significa que você iniciar o método em um segmento separado e depois continuar com outras operações.

Para executar operações assíncronas através de uma conexão System.Data.SqlClient.SqlConnection, você deve especificar o valor de Asynchronous Processing como sendo igual a true na string de conexão.

A classe SqlCommand implementa o padrão de execução assíncrono onde os argumentos dos métodos de execução assíncronos (BeginExecuteNonQuery, BeginExecuteReader e BeginExecuteXmlReader) são os mesmos que os usados no modo síncrono.

A diferença básica é que eles usam dois argumentos adicionais para suporta a operação assíncrona. Os quais são:

Os métodos EndExecuteNonQuery, EndExecuteReader e EndExecuteXmlReader permitem a você recuperar o valor de retorno de uma operação que foi executada de forma assíncrona, mas primeiro você deve determinar quando ela terminou.

Temos quatro técnicas para determinar se um método assíncrono terminou:

Blocking : Este método pára a execução da thread atual até que a operação assíncrona concluir sua execução. Este é o mesmo que o usado na execução síncrona. No entanto, neste caso, você tem a flexibilidade para decidir exatamente quando seu código entra no estado bloqueado, dando-lhe a oportunidade de realizar algum processamento adicional antes de bloquear;

Polling: Este método envolve testar repetidamente o estado de uma operação assíncrona para determinar se ela está completa. Esta é uma técnica muito simples e não é particularmente eficiente do ponto de vista de processamento. Você deve evitar vincular loops que consomem tempo do processador. É melhor colocar a thread pooling para dormir por um período entre os testes usando Thread.Sleep.

Waiting : Este método usa um objeto derivado da classe System.Threading.WaitHandle para sinalizar quando o método assíncrono for concluído. Waiting é uma versão mais eficiente de pesquisa e, além disso permite a você esperar por múltiplas operações assíncronas serem concluídas. Você também pode especificar valores de tempo limite para permitir que a sua thread waiting falhe se operação assíncrona demorar muito tempo ou se você deseja periodicamente atualizar o estado do indicador;

CallBack : Esse é um método que o runtime chama quando uma operação assíncrona é concluída. O código Calling não precisa tomar todas as medidas para determinar quando a operação assíncrona está completa e é livre para continuar com outros processamento. Usar Callbacks proporciona a maior flexibilidade, mas também introduz uma maior complexidade, especialmente se você tiver muitos operações assíncronas que usam o mesmo callback. Nesses casos, você deve usar objetos de estado apropriado para combinar com métodos completados contra aqueles que iniciado.

Ufa ! Agora vamos à prática.

  • Recursos usados:

    Criando uma conexão assíncrona no SQL Server

    Para atingir o nosso objetivo vamos realizar as seguintes tarefas:

    1- Definindo o banco de dados SQL Server

    Neste exemplo eu vou acessar a tabela Customers do banco de dados Northwind.mdf do SQL Server. A estrutura da tabela é vista abaixo:

    A string de conexão definida no meu ambiente para este banco de dados é vista abaixo:

    "Data Source=.\sqlexpress;Initial Catalog=Northwind;Integrated Security=True"

    Atenção ! Para o seu ambiente esse valor pode ser diferente.

    2- Criando o projeto ASP .NET e definir a página assíncrona

    Abra o VS Community 2013 clique em New Project;

    Selecione Visual Basic (ou Visual C#) -> web e o template ASP .NET Web Application e informe o nome ASPNET_ConexaoAssincrona e clique no botão OK;

    A seguir selecione o template Empty e marque a opção Web Forms e clique no botão OK;

    No menu PROJECT clique em Add New Item e a seguir selecione o template Web Form e informe o nome Default.aspx;

    Abra a página Default.aspx no modo Source e inclua o atributo Async="true" na diretiva Page da página. Ao fazer isso estamos dizendo à ASP .NET para implementar IHttpAsyncHandler na página.

    Vamos definir também ou timeout para a página concluir a tarefa assíncrona incluindo o atributo AsyncTimeout="20" na diretiva Page:

    <%@ Page Language="vb" Async="true" AsyncTimeout="20" AutoEventWireup="false" CodeBehind="Default.aspx.vb" Inherits="ASPNET_ConexaoAssincrona._Default" %>
    
    <!DOCTYPE html>
    ....

    Agora abra o arquivo Web.Config do projeto e defina a string de conexão entre as tagas <connectionstrings> com o banco de dados Northwind.mdf conforme abaixo:

    <?xml version="1.0" encoding="utf-8"?>
    <!--
      For more information on how to configure your ASP.NET application, please visit
      http://go.microsoft.com/fwlink/?LinkId=169433
      -->
    <configuration>
      <system.web>
        <compilation debug="true" strict="false" explicit="true" targetFramework="4.5" />
        <httpRuntime targetFramework="4.5" />
      </system.web>
    <connectionStrings>
      <add name="ConexaoNorthwind" connectionString="Data Source=.\sqlexpress;Initial Catalog=Northwind;Integrated Security=True" providerName="System.Data.SqlClient" />
    </connectionStrings>
    </configuration>

     

    Ainda no arquivo Web.Config vamos incluir na seção <appSettings> o elemento UseTaskFriendlySynchronizationContext que especifica como o código do path assíncrono se comporta.  Se este valor de chave for definido como false [default], os caminhos de código assíncrono em ASP.NET 4.5 se comportam como em ASP.NET 4.0.

       ....
      <appSettings>
        <add key="aspnet:UseTaskFriendlySynchronizationContext" value="false" />
      </appSettings>
       ....

    Agora vamos incluir na página Default.aspx um controle GridView (name=gdvDados) ,  a partir da guia Data da ToolBox, e selecionando o recurso Auto Format..., defina uma formatação conforme mostrado abaixo:

    Finalmente inclua um controle Button (name=btnAcessarSQLServer) logo abaixo do controle GridView.

    Definindo o código no arquivo code-behind Default.aspx.vb

    Vamos começar definindo os namespaces usados na página :

    Imports System.Data
    Imports
    System.Data.SqlClient
    Imports
    System.Configuration
    Imports
    System.Threading.Tasks

    A seguir no início da página vamos definir a variável conexão conn do tipo SqlConnection que iremos usar no projeto e a variável _progressoTarefa para armazenar informações da tarefa.

    'define a string de conexão obtida a partir do arquivo Web.Config (conexaoNorthwind)
    Dim conn As New SqlConnection(ConfigurationManager.ConnectionStrings("conexaoNorthwind").ConnectionString)

    'variável string para armzenar o texto referente ao progresso da tarefa (inicio, encerramento, falha)
    Private _progressoTarefa As String

    Agora vamos incluir no evento Click do botão de comando, no arquivo code-behind Default.aspx.vb o código onde fazermos o seguinte:
     

    O código pode ser visto abaixo:

     Protected Sub btnAcessarSQLServer_Click(sender As Object, e As EventArgs) Handles btnAcessarSQLServer.Click
            'Define uma tarefa assincrona
            Dim tarefaAssincrona As New PageAsyncTask(AddressOf BeginAsync, AddressOf EndAsync, AddressOf ExcedeuTempo, Nothing, True)
    
            'registra a tarefa assincrona
            Me.RegisterAsyncTask(tarefaAssincrona)
    
            ' Executa o registro da tarefa assincrona
            Page.ExecuteRegisteredAsyncTasks()
    
            'exibe as mensagens na Label
            lblmsg.Text = _progressoTarefa
        End Sub

    Usamos a classe PageAsyncTask que contém informações sobre uma tarefa assíncrona registrada para uma página e definimos os métodos:

    O código para estes métodos pode ser visto abaixo:

        'define o método que será chamado para iniciar a tarefa
        Private Function BeginAsync(sender As Object, e As EventArgs, cb As AsyncCallback, state As Object) As IAsyncResult
            _progressoTarefa = "Tarefa assíncrona iniciada em : " + DateTime.Now.ToString + ". " + vbCrLf + vbCrLf
            conn.Open()
            Dim cmd As New SqlCommand("Select * from Customers WAITFOR DELAY '00:00:02'", conn)
            Dim adp As New SqlDataAdapter(cmd)
            Dim ds As New DataSet()
            adp.Fill(ds)
            dgvDados.DataSource = ds
            dgvDados.DataBind()
            Dim ar As IAsyncResult = cmd.BeginExecuteNonQuery(cb, cmd)
            Return ar
        End Function
    
        'define o método que será chamado para encerrar a tarefa
        Private Sub EndAsync(ar As IAsyncResult)
            'define a mensagem para o encerramento
            _progressoTarefa += "Tarefa Assíncrona completada em : " + DateTime.Now.ToString 
            Using cmd As SqlCommand = DirectCast(ar.AsyncState, SqlCommand)
                Using cmd.Connection
                    Dim rows As Integer = cmd.EndExecuteNonQuery(ar)
                End Using
            End Using
        End Sub
    
        ' Define o método que será chamado se a tarefa não for completada dentro do intervalo de tempo 
        Private Sub ExcedeuTempo(ByVal ar As IAsyncResult)
            _progressoTarefa += "Tarefa Assíncrona falhou ao completar porque o parâmetro AsyncTimeout foi excedido."
        End Sub

    No método BenginAsync temos a instrução SQL SELECT utiliza WAITFOR DELAY que bloqueia a execução de um lote, procedimento armazenado ou transação até que uma hora ou intervalo de tempo especificado seja alcançado ou que uma instrução especificada modifique ou retorne pelo menos uma linha. No exemplo DELAY refere-se ao tempo que deve ser esperado e vale 00:00:02.

    A seguir usamos a instrução BeginExecuteNonQuery() que inicia a execução assíncrona da instrução ou procedimento armazenado de Transact-SQL que é descrito por esse SqlCommand.

    No método EndAsync encerramos a execução assíncrona usando a instrução EndExecuteNonQuery().

    Executando o projeto iremos obter o seguinte resultado:

    Pegue o projeto completo aqui :   ASPNET_ConexaoAssincrona.zip

    Disse-lhes Jesus: Em verdade, em verdade vos digo que antes que Abraão existisse, Eu Sou.
    João 8:58

    Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

    Quer migrar para o VB .NET ?

    Quer aprender C# ??

    Quer aprender os conceitos da Programação Orientada a objetos ?

    Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

      Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

    Referências:


    José Carlos Macoratti