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. 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:
class Program
{
public static void Main(string[] args)
{
var webClient1 = new WebClient();
webClient1.DownloadStringCompleted += (s1, e1) => {
Console.WriteLine(e1.Result);
};
webClient1.DownloadStringAsync(new Uri("http://silverlightrush.blogspot.com"));
Console.ReadLine();
}
}
Ok, esse código quando rodado irá instanciar uma classe chamada WebClient. Esta classe contém um método chamado 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 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:
var webClient1 = new WebClient();
webClient1.DownloadStringCompleted += (s1, e1) =>
{
if (e1.Result.Length < 1000)
Console.WriteLine(e1.Result);
else {
var webClient2 = new WebClient();
webClient2.DownloadStringCompleted += (s2, e2) => {
Console.WriteLine(e2.Result);
};
webClient2.DownloadStringAsync(new Uri("http://google.com"));
};
};
webClient1.DownloadStringAsync(new Uri("http://silverlightrush.blogspot.com"));
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:
class Program
{
static string htmlBlogspot;
static string htmlGoogle;
public static void Main(string[] args)
{
Coroutine.BeginExecute(Operacoes());
Console.ReadLine();
}
static IEnumerable<Routine> Operacoes() {
yield return PrimeiraOperacao;
if (htmlBlogspot.Length < 1000) {
Console.WriteLine(htmlBlogspot);
yield break; //cancela a execução da próxima
}
yield return SegundaOperacao;
Console.WriteLine(htmlGoogle);
}
static void PrimeiraOperacao(Action completed) {
var webClient1 = new WebClient();
webClient1.DownloadStringCompleted += (s1,e1) => {
htmlBlogspot = e1.Result;
completed(); //marca o fim da operação
};
webClient1.DownloadStringAsync(new
Uri("http://silverlightrush.blogspot.com"));
}
static void SegundaOperacao(Action completed) {
var webClient2 = new WebClient();
webClient2.DownloadStringCompleted += (s2, e2) => {
htmlGoogle = e2.Result;
completed(); //marca o fim da operação
};
webClient2.DownloadStringAsync(new Uri("http://google.com"));
}
}
O novo código:
- A co-rotina é inicializada por Coroutine.BeginExecute(Operacoes())
- A co-rotina é executada linha a linha dentro do método Operacoes()
- O método Operacoes() retorna IEnumerable<Routine>
- Cada operação assíncrona é executada no momento em que utilizamos yield return
- A co-rotina pode ser cancelada com yield break;
- Cada rotina ficou separada em seu próprio método. Quando a rotina completa, chama-se a action 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<Routine>, 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.
Nenhum comentário:
Postar um comentário