C# - Realizando operações assíncronas em um banco de dados SQL Server


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 ExecuteNonQuery,ExecuteReader e ExecuteXmlReader 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.

Exemplo prático

Vejamos um exemplo prático para ilustrar a teoria explanada acima.

Abra o Visual C# 2010 Express Edition e crie uma aplicação do tipo Console Application com o nome OperacaoAssincrona;

Neste exemplo vamos usar a stored procedure Ten Most Expensive Products do banco de dados Northwind.mdf do SQL Server 2008:

A seguir defina o seguinte código :

using System;
using System.Data;
using System.Threading;
using System.Data.SqlClient;

namespace Macoratti
{
    class OperacaoAssincrona
    {
        //  Um método para lidar com a conclusão assíncrona usando callbacks.
        public static void CallbackHandler(IAsyncResult resultado)
        {
            // Obtém uma referência ao SqlCommand usado para iniciar a operação assincrona
            using (SqlCommand cmd = resultado.AsyncState as SqlCommand)
            {
                // Obtém o resultado da stored procedure.
                using (SqlDataReader reader = cmd.EndExecuteReader(resultado))
                {
                    // Exibe os resultados da stored procedure no console.
                    lock (Console.Out)
                    {
                        Console.WriteLine( "Preço dos Dez Produtos mais Caros:");
                        while (reader.Read())
                        {
                            // Exibe detalhes do produto
                            Console.WriteLine("  {0} = {1}", reader["TenMostExpensiveProducts"], reader["UnitPrice"]);
                        }
                    }
                }
            }
        }

        public static void Main()
        {
            // Cria um novo objeto SqlConnection
            using (SqlConnection con = new SqlConnection())
            {
                // Configura a string de conexão do objeto SqlConnection
                // Você precisa especificar Asynchronous Processing=true para suportar
                // operações assíncronas na conexão

                con.ConnectionString = @"Data Source = .\sqlexpress;" +
                                                     "Database = Northwind; Integrated Security=SSPI;" +
                                                     "Asynchronous Processing=true";

                // Cria e configura um novo comando para rodar a stored procedure.
                SqlCommand cmd = con.CreateCommand();
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "Ten Most Expensive Products";

                // Abra a conexão com o banco de dados e executa o comando
                // assincronamente. Passa a referencia para o SqlCommand
                // usado para iniciar a operação assincrona

                con.Open();
                cmd.BeginExecuteReader(CallbackHandler, cmd);

                // Continua com outro processamento
                for (int count = 0; count < 10; count++)
                {
                    lock (Console.Out)
                    {
                        Console.WriteLine("{0} : Continua o processamento...",DateTime.Now.ToString("HH:mm:ss.ffff"));
                    }
                    Thread.Sleep(500);
                }
            }

            // Espera para continuar
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("O método Main concluiu sua operação. Tecle Enter.");
            Console.ReadLine();
        }
    }
}

O código já esta comentado e dispensa mais detalhes.

Executando a aplicação iremos obter:

Pegue o projeto completo com os exemplos aqui : OperacaoAssincrona.zip

1Pedro 2:6 Por isso, na Escritura se diz: Eis que ponho em Sião uma principal pedra angular, eleita e preciosa; e quem nela crer não será confundido.
1Pedro 2:7
E assim para vós, os que credes, é a preciosidade; mas para os descrentes, a pedra que os edificadores rejeitaram, esta foi posta como a principal da esquina,

1Pedro 2:8
e: Como uma pedra de tropeço e rocha de escândalo; porque tropeçam na palavra, sendo desobedientes; para o que também foram destinados.

Referências:


José Carlos Macoratti