Utilizando Delegates com Co-Rotinas - parte 1
Autor: Jone | Publicado: 2013-01-28T00:15:00-08:00

No&nbsp;post anterior&nbsp;comentei sobre delegates, funcs e actions, mas infelizmente não dei exemplos práticos de utilização.

Nesta primeira parte do post, introduzirei o conceito de co-rotina e um exemplo de utilização.


Co-Rotinas

Co-rotinas facilitam a programação e visualização de código quando temos a necessidade de fazer chamadas assíncronas de forma sequencial, ou seja, esperar o resultado de uma operação antes de iniciar a próxima operação, permitindo que tomemos decisões de acordo com os resultados obtidos.

Na programação síncrona, o fluxo da execução de um programa ocorre em uma mesma Thread. Na programação assíncrona, uma outra Thread é criada para execução de uma determinada tarefa que tem um tempo de duração indefinido, como por exemplo, chamadas a serviços da Web.

Geralmente os métodos assíncronos possuem eventos ou callbacks que são disparados no momento em que a operação for finalizada/completada, permitindo que o programador escreva código para tratar o resultado. &nbsp;A dor de cabeça vem quando a lógica de uma aplicação fica complexa, por exemplo, quando há a necessidade de fazer várias chamadas utilizando eventos e callbacks, tornando o código difícil de ler, e sujeito a bugs.

Para exemplificar, vamos escrever um programa simples que faz o download do código html de uma página, e imprime o conteúdo numa aplicação console:

&nbsp; &nbsp; &nbsp;class&nbsp;Program
&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;Main(string[]&nbsp;args)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;webClient1 =&nbsp;new&nbsp;WebClient();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webClient1.DownloadStringCompleted&nbsp;+=&nbsp;(s1,&nbsp;e1)&nbsp;=&gt;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Console.WriteLine(e1.Result);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webClient1.DownloadStringAsync(new&nbsp;Uri("http://silverlightrush.blogspot.com"));&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Console.ReadLine();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

Ok, esse código quando rodado irá instanciar uma classe chamada&nbsp;WebClient. Esta classe contém um método chamado&nbsp;DownloadStringAsync. Quando executado, o código não "espera" o download terminar, pois é um método assíncrono, e então segue para a próxima linha. Antes de chamar o método, devemos subscrever ao evento&nbsp;DownloadStringCompleted. Através de uma expressão lambda, imprimimos o conteúdo da página toda na tela.

Agora vamos inserir uma regra de negócio ao programa. Digamos que ao terminar de ler o html, caso o Lenght do resultado seja maior que 1000 bytes, faremos uma outra chamada para baixar o html de um outro site. Então o novo código ficou definido assim:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;var&nbsp;webClient1 =&nbsp;new&nbsp;WebClient();&nbsp; &nbsp; webClient1.DownloadStringCompleted&nbsp;+=&nbsp;(s1,&nbsp;e1)&nbsp;=&gt;&nbsp;
&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(e1.Result.Length&nbsp;&lt;&nbsp;1000)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Console.WriteLine(e1.Result);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;var&nbsp;webClient2 =&nbsp;new&nbsp;WebClient();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; webClient2.DownloadStringCompleted&nbsp;+=&nbsp;(s2,&nbsp;e2)&nbsp;=&gt;&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Console.WriteLine(e2.Result);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; webClient2.DownloadStringAsync(new&nbsp;Uri("http://google.com"));
&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; };&nbsp; &nbsp; webClient1.DownloadStringAsync(new&nbsp;Uri("http://silverlightrush.blogspot.com"));
&nbsp; &nbsp; Console.ReadLine();

Pronto. A nossa primeira regra de negócio foi inserida. O código funciona perfeitamente. Mas e se agora quisermos inserir uma nova regra baseado no resultado da segunda chamada ? Repare que o código está difícil de ler e provavelmente vai ficar difícil de dar manutenção.


Utilizando Co-Rotinas

Utilizando a técnica de co-rotinas, escrevemos código sequencial, enfileirando diversas chamadas assíncronas, utilizamos os tradicionais callbacks apenas dar prosseguimento ao fluxo de trabalho.



Vamos ver como fica a sintaxe para esse mesmo programa mas com a utilização de co-rotinas:

&nbsp; &nbsp;&nbsp;class&nbsp;Program
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;static&nbsp;string&nbsp;htmlBlogspot;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;static&nbsp;string&nbsp;htmlGoogle;
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;Main(string[]&nbsp;args)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Coroutine.BeginExecute(Operacoes());&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Console.ReadLine();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;static&nbsp;IEnumerable&lt;Routine&gt;&nbsp;Operacoes()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield&nbsp;return&nbsp;PrimeiraOperacao;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(htmlBlogspot.Length&nbsp;&lt;&nbsp;1000)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Console.WriteLine(htmlBlogspot);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield&nbsp;break;&nbsp;//cancela a execução da próxima
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield&nbsp;return&nbsp;SegundaOperacao;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Console.WriteLine(htmlGoogle);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;static&nbsp;void&nbsp;PrimeiraOperacao(Action completed)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;webClient1 =&nbsp;new&nbsp;WebClient();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webClient1.DownloadStringCompleted&nbsp;+=&nbsp;(s1,e1)&nbsp;=&gt;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;htmlBlogspot = e1.Result;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;completed(); //marca o fim da operação
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webClient1.DownloadStringAsync(new
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Uri("http://silverlightrush.blogspot.com"));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;static&nbsp;void&nbsp;SegundaOperacao(Action completed)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;webClient2 =&nbsp;new&nbsp;WebClient();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webClient2.DownloadStringCompleted&nbsp;+=&nbsp;(s2,&nbsp;e2)&nbsp;=&gt;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;htmlGoogle = e2.Result;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;completed(); //marca o fim da operação
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webClient2.DownloadStringAsync(new&nbsp;Uri("http://google.com"));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}



O novo código:

A co-rotina é inicializada por&nbsp;Coroutine.BeginExecute(Operacoes())&nbsp;
A co-rotina é executada linha a linha dentro do método&nbsp;Operacoes()
O método&nbsp;Operacoes()&nbsp;retorna IEnumerable&lt;Routine&gt;
Cada operação assíncrona é executada no momento em que utilizamos&nbsp;yield&nbsp;return
A co-rotina pode ser cancelada com&nbsp;yield&nbsp;break;
Cada rotina ficou separada em seu próprio método. Quando a rotina completa, chama-se a action&nbsp;completed()




Como isso é possível ?


O Código abaixo é responsável pela enumeração e execução sequencial da co-rotina. Através da iteração do método que retorna IEnumerable&lt;Routine&gt;, o compilador C# faz uma mágica criando uma State Machine.










Delegates



Repare que foi criado um Delegate especial chamada Routine. Este delegate é utilizado no enumerador da co-rotina. A action que se passa como parâmetro é uma referência a um callBack que devemos executar para dar continuação à enumeração (MoveNext()).



Para ver o código completo no GitHub, clica no link abaixo.

Programa Completo