C# - Programação Assincrona usando async e await
A programação assíncrona tem sido um território onde poucos se atrevem a se aventurar pois até o momento esse território tem sido muito pantanoso e cheio de areia movediça. |
Com o advento da Microsoft .NET Framework 4.5 isso mudou; a partir dessa versão tanto a linguagem C# como a VB .NET permitem que um "programador comum" possa escrever métodos assíncronos da mesma forma que escrevia métodos síncronos.
Todo esforço necessário para contornar os problemas oriundos da programação assíncrona que os programadores tinham que fazer para que os seus métodos assíncronos pudessem funcionar foi abstraído pelos novos recursos.
Você pode apenas usar o recurso sem se preocupar com o que esta ocorrendo nos bastidores ou pode querer entender qual o custo essa abstração vai ter para continuar tendo um controle total sobre o seu código.
Embora
as linguagens C# e Visual Basic e os compiladores sejam
capazes de fornecer a ilusão de que um método
assíncrono é idêntico ao seu correspondente síncrono,
as coisas são diferentes nos bastidores. O compilador acaba gerando muitos códigos em nome do desenvolvedor, códigos que são similares às quantidades de códigos de texto clichê que os desenvolvedores que implementavam assincronicidade antigamente teriam de escrever e manter manualmente. Além disso, o código gerado pelo compilador chama um código de biblioteca no .NET Framework, mais uma vez aumentando o trabalho realizado em nome do desenvolvedor. |
Neste artigo vamos abordar os conceitos básicos e a utilização de métodos assíncronos no C# e no Visual Basic.
As linguagens de programação Visual Basic e C# são imperativas, isso significa que elas permitem que você expresse a sua lógica de programação como uma sequência de etapas separadas a serem executadas uma após a outra.(Ref2)
Atualmente tanto o C# como o VB .NET oferecem métodos síncronos e assíncronos e por padrão qualquer método que você crie normalmente no seu dia a dia é síncrono.
A seguir temos um exemplo de código síncrono que acessa um banco de dados e preenche um controle TextBox em um formulário com dados. Esse código é totalmente síncrono.
private void CarregaDados() { SqlConnection conn = new SqlConnection(@"Data Source=.\sqlexpress;Initial Catalog=Cadastro;Integrated Security=True"); string sql = @"select Id,Nome from Alunos"; try{ SqlCommand cmd = new SqlCommand(sql,conn); conn.Open(); SqlDataReader rdr = cmd.ExecuteReader(); while (rdr.Read()){ txtDados.AppendText("\nId: "); txtDados.AppendText(rdr.GetValue(0) + "\t\t" + rdr.GetValue(1)); txtDados.AppendText("\n"); } } catch (SqlException ex){ MessageBox.Show(ex.Message + ex.StackTrace, "Detalhes Exception"); } finally{ conn.Close(); } } |
Na maioria das linguagens imperativas, incluindo as versões atuais do Visual Basic e do C#, a execução dos métodos (ou das funções, ou dos procedimentos) é contínua. Ou seja, uma vez que uma thread de controle comece a executar um determinado método, ela ficará ocupada com essa tarefa até que a execução do método seja concluída.
Às vezes a thread estará executando instruções em métodos chamados pelo seu código, mas isso faz parte da execução do método. A thread nunca fará algo que não foi solicitado pelo seu método.(Ref2)
Às vezes, essa continuidade, esse sincronismo é um problema. Às vezes, não há nada que um método possa fazer para progredir tudo o que ele pode fazer é esperar que algo aconteça: um download, o acesso a um arquivo, o cálculo executado em uma thread diferente, etc. Nesses casos, a thread fica totalmente ocupada sem fazer nada.
O termo geralmente usado nessas situações é: thread bloqueado; o método que causa isso é conhecido como bloqueador.(Ref2)
Mas o que há de errado com esse comportamento ?
O comportamento síncrono pode deixar os usuários finais com uma má experiência e uma interface bloqueada/congelada sempre que o usuário tentar realizar alguma operação demorada.
Na figura abaixo temos um exemplo clássico onde durante a carga dos dados a interface do usuário fica congelada aguardando o processamento.
Qual a solução para este problema ?
A chamada do método síncrono pode criar um atraso na execução do programa que provoca uma má experiência do usuário. Por isso, uma abordagem assíncrona (threads) seria melhor.
Uma chamada de método assíncrono (criação de uma thread) irá retornar imediatamente para que o programa possa realizar outras operações enquanto o método chamado conclui o seu trabalho em determinadas situações.
O comportamento do método assíncrono é diferente do síncrono, porque um método assíncrono é uma thread separada. Você cria a thread; a thread começa a executar, mas o controle é imediatamente retornado para a thread que a chamou, enquanto a outra thread continua a executar.
Em geral, a programação assíncrona faz sentido em dois cenários:
A partir da versão 5.0 da linguagem C# o modificador async/wait e Async e Await no Visual Basic, oferecem uma maneira completamente diferente e fácil de fazer programação assíncrona.
As palavras chave Async e Await em Visual Basic e ascyn e await em C# são o coração de programação assíncrona.
Ao utilizar essas duas palavras-chave, você pode usar os recursos do. NET Framework ou o Windows Runtime para criar um método assíncrono quase tão facilmente como você cria um método síncrono. Os métodos assíncronos que você define usando esses recursos referidos como métodos assíncronos.
Como resultado, o código assíncrono é fácil de implementar e manter a sua estrutura lógica. Por isso agora é tão fácil escrever código assíncrono como escrever o seu método normal, sem preocupação de qualquer canalização extra.
Considere o exemplo usado no início deste artigo onde temos uma interface WPF UI com vinculação de dados, buscando um grande número registros em um banco de dados. Enquanto os dados estão sendo acessados e tratados, o resto da interface do usuário deve continuar a ser responsiva. Qualquer tentativa de interação com outros controles de interface do usuário não devem ser bloqueadas e e o carregamento de dados e sua vinculação com os controles deve continuar em paralelo.
Criando código assíncrono com async e await
Vamos então transformar o código para carregar os dados no formulário de síncrono para assíncrono.
Para criar código assíncrono usamos as palavras chaves async e await onde por padrão um método modificado por uma palavra-chave async contém pelo menos uma expressão await.
O método é executado de forma síncrona até que ele alcance a primeira expressão await, e neste ponto o método é suspenso até que a tarefa seja completada. Enquanto isso , o controle retorna para o chamador do método, como o exemplo neste tópico mostra.
Se um método que esta sendo modificado por uma palavra-chave async não contém uma expressão ou uma instrução await, o método é executado de forma síncrona. Um aviso do compilador o avisa sobre qualquer método assíncrono que não contiver um await porque essa situação pode indicar um erro.
Quando async modifica um método, uma expressão lambda ou um método anônimo, async é uma palavra-chave. Em todos os outros contextos, async é interpretado como um identificador. Esta distinção faz async uma palavra-chave contextual.
Um método async (assíncrono) pode ter um tipo de retorno Task, Task(Of TResult) ou void. O método não pode declarar qualquer parâmetro ref ou out, embora ele pode chamar métodos que tenham esses parâmetros.
Em métodos assíncronos, você usa as palavras-chave e tipos para indicar o que você quer fazer, e o compilador faz o resto, incluindo manter o controle do que deve acontecer quando o controle retorna para um ponto await em um método suspenso.
Não pense que você pode sair por ai usando async e await indiscriminadamente. Existem algumas regras básicas que devem ser seguidas. A seguir algumas regras importas para o uso de async e await:
Agora veja como ficou o código do exemplo usando async e await:
private async void CarregarDadosAssincrono() { SqlConnection conn = new SqlConnection(@"Data Source=.\sqlexpress;Initial Catalog=Cadastro;Integrated Security=True"); string sql = @"select Id,Nome from Alunos"; try { SqlCommand cmd = new SqlCommand(sql, conn); await conn.OpenAsync(); using (SqlDataReader rdr = await cmd.ExecuteReaderAsync()) { while (await rdr.ReadAsync()) { txtDados.AppendText("\nId: "); txtDados.AppendText(await rdr.GetFieldValueAsync<int>(0) + "\t\t" + await rdr.GetFieldValueAsync<string>(1)); txtDados.AppendText("\n"); } } } catch (SqlException ex) { MessageBox.Show(ex.Message + ex.StackTrace, "Detalhes Exception"); } finally { conn.Close(); } } |
Obs: Naturalmente a utilização do código assíncrono somente se justifica em cenários de grande volume de dados ou complexidade de processamento quando o tempo impacta o desempenho.
Este código além de usar async e await também usa os métodos OpenASync(), ExecuteReaderAsync(), ReadAsync() e GetFieldValueAsync<T>() da versão 4.5.
É importante decidir antes de criar um SqlDataReader se voceê precisa usar o modo de acesso não-sequencial ou sequencial. Na maioria dos casos, utilizar o modo de acesso padrão (não-sequencial) é a melhor opção, pois permite um modelo de programação mais fácil (você pode acessar qualquer coluna em qualquer ordem), e você obterá um melhor desempenho usando ReadAsync.
No entanto, desde que o modo de acesso não sequencial tem de armazenar os dados para toda a linha, ela pode causar problemas se você estiver lendo uma grande coluna do servidor (como varbinary (MAX), varchar (max), nvarchar (MAX) ou XML). Neste caso, usando o modo de acesso seqüencial lhe permitirá transmitir estas grandes colunas ao invés de ter que colocar a coluna inteira na memória.
É também uma boa ideia chamar ReadAsync : no modo não-seqüencial isso vai ler todas os dados da coluna, o que pode abranger vários pacotes, permitindo o acesso mais rápido aos valores da coluna.
A seguir uma breve descrição dos métodos usados:
Pegue o projeto completo aqui: ProgramacaoAssincrona.zip
João 7:37
Ora, no seu último dia, o grande dia da festa, Jesus pôs-se em pé e clamou, dizendo: Se alguém tem sede, venha a mim e beba.João 7:38
Quem crê em mim, como diz a Escritura, do seu interior correrão rios de água viva.Referências:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#
(Ref1) -http://msdn.microsoft.com/pt-br/magazine/hh456401.aspx
(Ref2)- http://msdn.microsoft.com/pt-br/magazine/hh456402.aspx
http://msdn.microsoft.com/en-us/library/vstudio/hh156513.aspx
Best Practices in Asynchronous Programming - http://msdn.microsoft.com/en-us/magazine/jj991977.aspx