Asynchronous Method Invocation usando Delegates

Seguindo a sugestão do meu amigo Diego Azevedo vou demonstrar neste post como fazer a chamada de um método de forma assíncrona utilizando um padrão conhecido como Asynchronous Method Invocation (AMI).

Vou limitar o escopo desse post a um método de uma classe que possui somente um parâmetro e que retorna um valor.

Na maioria das linguagens de programação por padrão um método é executado de forma síncrona, ou seja, quando chamamos o método este é executado e somente quando termina é que a linha de execução do programa continua.

Por exemplo, no código abaixo a mensagem "Terminei !" só é mostrada quando o método FacaAlgumaCoisa é finalizado.

Classe objeto = new Classe();
objeto.FacaAlgumaCoisa();
Console.WriteLine("Terminei !")

Digamos que NÃO queremos que o usuário aguarde que o método FacaAlgumaCoisa termine para continuar a execução do programa. Queremos então estar aptos a utilizar o programa e fazer outras operações enquanto o método é executado. Temos então que desenvolver uma aplicação que seja multi-tarefa ou multi-threading em inglês.

Não quero entrar muito a fundo no conceito de multi-threading e deixo esse estudo como "dever de casa".

O código abaixo será uma aplicação console C# em Visual Studio 2008 Professional SP1 usando o .NET 3.5. É possível também utilizarmos a versão Express sem problemas.

Vamos começar criando uma classe qualquer que iremos utilizar em nosso exemplo. Para simplificar, vamos criar a classe no arquivo Program.cs mesmo.

public class MinhaClasse
{
    // exemplo de método que demora em torno de 10 segundos
    // para ser executado e retorna um valor
    public int MetodoDemorado(int a)
    {
        System.Threading.Thread.Sleep(10000);
        return a + 10;
    }
}

A classe é bem simples e contém um método que possui um parâmetro do tipo int e retorna o resultado da soma deste com o valor 10. O método estático Sleep da classe Thread foi introduzido no exemplo somente para simular um processamento que leva em torno de 10 segundos para executar.

Vamos declarar então no método Main um objeto da classe MinhaClasse.

// cria instância da classe MinhaClasse
MinhaClasse objeto = new MinhaClasse();

A idéia deste post é executar o método MetodoDemorado de forma assíncrona. Para executar um método de forma assíncrona precisamos de um delegate. Delegate é um tipo que referencia um método, similar aos ponteiros para funções do C/C++ (você já deveria saber disso !).

Felizmente no .NET 3.5 temos, com ajuda de Generics, alguns delegates que encapsulam alguns métodos. Neste exemplo vou utilizar o delegate System.Func pois preciso de um delegate que receba um inteiro e retorne um inteiro.

Criamos então a instância do delegate passando o método MetodoDemorado como parâmetro, conforme o código abaixo.


// encapsula o método em um delegate
Func<int, int> async = new Func<int, int>(objeto.MetodoDemorado);

Obs: Se o método não retornasse valor poderíamos utilizar o delegate System.Action.

Para executar o método de forma assíncrona vamos utilizar o método do delegate BeginInvoke gerado pelo compilador. Teremos que passar então como argumentos um inteiro, que é o parâmetro do método MetodoDemorado, um método callBack chamado quando o método finalizar a execução e um objeto que representa um estado qualquer.

async.BeginInvoke(...);

O argumento para o método callBack é uma expressão lambda que representa um delegate anônimo, ou seja, um delegate que recebe um parâmetro IAsyncResult e não retorna valor. No código acima, r é o parâmetro IAsyncResult. Além disso, estamos passando o valor 10 como argumento do método MetodoDemorado e null para o estado.

async.BeginInvoke(10, (r) =>
{
... // aqui é o corpo do callBack
}, null);

Utilizamos o método EndInvoke do delegate async para pegar o valor retornado pelo método MetodoDemorado. Após a execução do método, mostramos uma mensagem com o resultado.

// executa o método de forma assíncrona
async.BeginInvoke(10, (r) =>
{
    // resultado do método
    int resultado = async.EndInvoke(r);
    
    // mostra resultado
    Console.WriteLine("Resultado: {0}", resultado);

}, null);

Vamos incluir após o método BeginInvoke uma mensagem para que a aplicação console não seja finalizada e também vamos utilizar o método ReadKey que irá aguardar com que o usuário clique em uma tecla para finalizar a aplicação.

Segue abaixo portanto o código completo.

using System;

namespace MetodoAssincrono
{
    public class MinhaClasse
    {
        // exemplo de método que demora em torno de 10 segundos
        // para ser executado e retorna um valor
        public int MetodoDemorado(int a)
        {
            System.Threading.Thread.Sleep(10000);
            return a + 10;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // cria instância da classe MinhaClasse
            MinhaClasse objeto = new MinhaClasse();

            // encapsula o método em um delegate
            Func<int, int> async = new Func<int, int>(objeto.MetodoDemorado);

            // executa o método de forma assíncrona
            async.BeginInvoke(10, (r) =>
            {
                // resultado do método
                int resultado = async.EndInvoke(r);
                
                // mostra resultado
                Console.WriteLine("Resultado: {0}", resultado);

            }, null);

            Console.WriteLine("Aguardando execução do método...");
            Console.ReadKey(true);
        }
    }
}

Se executarmos essa aplicação, na tela de prompt será informado...



Só aqui já podemos perceber que a execução do método foi assíncrona, visto que a mensagem encontra-se após o BeginInvoke. Se aguardarmos um pouco (+- 10 segundos) poderemos ver a mensagem de retorno do método MetodoDemorado.



Espero ter sido claro e conseguido compartilhar com vocês esse tipo de técnica. Vou criar futuramente um post semelhante em uma aplicação Windows Forms tratando operações cross-thread.

Segue abaixo alguns links interessantes:

Delegate (C# Programming Guide)
http://bit.ly/54QbSD

Lambda Expressions (C# Programming Guide)
http://bit.ly/87xAZJ

Anonymous Delegates (C# Programming Guide)
http://bit.ly/6jZdYS

Func Delegate
http://bit.ly/5nAva4

Asynchronous Programming Using Delegates
http://bit.ly/6tcwzD

3 comentários:

  1. Boa iniciativa Ari!
    Poderia ser Java, mas como orientação a objetos é td igual, também dá pra aplicar.. rs

    ResponderExcluir
  2. Oi Guilherme,

    Pois é, na verdade não trabalho com Java há um bom tempo (trabalhei com a versão 1.0) e não sei o que evoluiu na linguagem. Pelo que pesquisei, aparentemente Java não possui delegates, acredito que AMI só com threads mesmo.

    Um abraço !

    ResponderExcluir
  3. Olá Ari, muito claro seu artigo.
    Parabéns pela iniciativa !

    Abraço !

    ResponderExcluir