Às vezes eu fico pensando que seria interessante ao menos pra mim que o dia tivesse mais de 24 horas porque são tantas coisas a fazer e tantos planos que a cada dia que se acaba tenho a impressão de que as horas passam mais rápido.

Bom vamos ao que interessa, eu tinha planejado neste artigo falar um pouco sobre generics e demonstrar como o seu uso pode ser interessante se aplicado em algumas tarefas rotineiras no dia-a-dia dos programadores, mas uma colega de trabalho propôs um desafio interessante pra eu fazer. O desafio resumia-se a desenvolver uma aplicação windows com dois formulários um para que o usuário possa inserir as coordenadas x e y e o outro onde seria feita a busca das coordenadas dadas pelo usuário, sendo que a buscar deve ser feita em real time, ou seja, o usuário insere as coordenadas e a aplicação começa a buscar o diferencial é que enquanto um busca é feita o usuário pode pedir para a aplicação iniciar outras buscas. Essa aplicaçãozinha poderia ser feita usando um simples timer, por exemplo, se não fosse uma exigência do desafio que ao invés de timer eu usasse thread.

Como eu tenho mais experiência no desenvolvimento de aplicações web resolvi usar alguns recursos interessantes como, por exemplo: delegate, thread e chamada assíncrona de métodos. Eu vou explicar um por um dos recursos mencionados neste artigo.

É muito importante que antes de implementar uma solução com o uso de thread você saiba que os controles windows são baseados no conceito de STA - Single Thread Apartment, ou seja, qualquer tentativa de acesso a partir de uma thread ao formulário, controles do formulário ou a métodos criados no mesmo tem que ser feita a partir da thread que os criou.

Antes de comentar as forma que temos no .NET de utilizar chamadas a métodos e/ou controles apesar do STA gostaria de dar um breve mais importante definição sobre síncrono e assíncrono:

Síncrono: Acontece simultaneamente a outras tarefas ou em intervalos regulares gerenciado por um temporizador para manter a sincronia.

Assíncrono: Acontece de forma independente das tarefas que estão em execução e não é gerenciado por temporizador algum.

Para superar essa limitação se é que pode-se dizer isso, temos duas possibilidades:

Podemos implementar o acesso assíncrono usando o método BeginInvoke que está disponível em todos controles windows e também em delegates. Esse método força a execução assincrona na thread que originou o controle ou o método e retorna um objeto do tipo IAsyncResult. Existe ainda a possibilidade de usar um outro método que complementa o uso do BeginInvoke é o EndInvoke que é executado após a execução da chamada assíncrona, pois caso o método chamado com o uso do BeginInvoke retorne algum dado é preciso recupera-lo usando o EndInvoke que recebe como parâmetro um objeto do tipo IAsyncResult, assim como ilustra a listagem 01.

Listagem 01. Chamada assíncrona deum étodo existente em um outro formulário.

        IAsyncResult ar = frm2.fDelegate.BeginInvoke(Convert.ToInt32(this.txtX.Text), Convert.ToInt32(this.txtY.Text), null, null);    

            ar.AsyncWaitHandle.WaitOne();

 

            bool termino = frm2.fDelegate.EndInvoke(ar);

            if(termino)

                MessageBox.Show("Deu certo !");

            else

                MessageBox.Show("Ops, deu errado !");
        

Como podemos ver na listagem 01 existe uma linha destacada, essa linha é responsável por bloquear a execução do método EndInvoke antes do término da execução da chamada assíncrona. Esse bloqueio e feito por que o objeto retornado pelo método BeginInvoke IAsyncResult tem uma propriedade AsyWaitHandle que vai disparar uma resposta positiva quando a chamada for concluída. Esse retorno não tem nada haver com o retorno do método chamado assincronamente.

Podemos implementar o acesso síncrono usando o método Invoke que também esta disponível em todos controles windows. Por outro lado seu funcionamento é bem diferente do BeginInvoke que foi explicado acima, o método Invoke vai bloquear a thread que originol o controle ou o método para executar a sua chamada. O retorno do método executado também é recuperado de forma diferente, no caso do Invoke um possível retorno do método executado pode ser obtido diretamente sem o intermédio de um objeto como no caso anterior. Como na listagem 02. Os controles windows podem contar ainda com uma propriedade que valida se a chamada foi feita pela thread que o origiou essa propriedade é InvokeRequired.

Listagem 02. Chamada síncrona de um método existente em um outro formulário.

            object retorno = txtX.Invoke(frm2.fDelegate);
        

Pronto agora que já sabemos como pra que serve e como usamos os métodos BeginInvoke e Invoke vamos a falar um pouco sobre delegates já que pra utilizar os métodos assíncrono e síncrono vamos precisar de um delegate.

Na minha concepção um delegate é como se fosse um ponteiro.

O que é um ponteiro? Nada mais é que um valor aponta para outro valor alocado em memória através do seu endereço.

Então um delegate é um tipo de dado que criamos para apontar para algo, no nosso caso vamos usa-lo para chamar um método, assim como a listagem 03 ilustra.

Listagem 03. Criação de um delegate.

            public delegate bool delegateteste(int x ,int y);

            public delegateteste fDelegate;
        

Na listagem 03 podemos ver como declarar um delegate, na declaração de um delegate temos que especificar que tipo de método ele vai executar nesse caso ele só pode executar métodos que recebam dois inteiros como parâmetro e que retornem um tipo booleano.

Agora vamos entender o que é uma thread, é uma forma de dividir o processamento de um processo de forma que as tarefas(threads) sejam executadas simultaneamente. É interessante saber isso porque já ouvi muita gente dizer que thread são processos e não é bem verdade e isso é fácil constatar já que um thread compartilha, por exemplo, do espaço de memória do processo origem, assim como outros recursos. Mais esse é um recurso muito interessante se bem aplicado, pois dificilmente todas threads do seu processo vão ficar bloqueadas e isso vai agilizar consideravelmente sua aplicação.

Agora que já entendemos o funcionamento de todos recursos utilizados neste artigo vamos a explicação da implementação do desafio.

Como eu havia dito vamos ter 2 formulários, um onde o usuário insere as coordenas e inicia a busca, assim como ilustra a figura 01.

Imagem
Figura 01.. Formulário onde o usuário insere as coordenadas a serem encontradas.

E um segundo formulário onde vão ser executadas as buscas das coordenadas referente as dimensões do formulário, assim como ilustra a figura 02.

Imagem
Figura 02. Formulário onde vai ser feita a busca das coordenadas informadas pelo usuário.

Para buscar as coordenadas eu fiz simplesmente dois loop para percorrer os pixels no eixo X e outro no eixo Y. E implementei um método para desenhar um retângulo no formulário para demonstrar qual pixel esta sendo lido naquele momento, assim como ilustra a listagem 04.

Listagem 04. Método que desenha um retângulo nas coordenadas correntes dos loops.

            private void Desenha(Graphics g,int x, int y, bool apaga) {

                if(apaga)
          
                      g.DrawRectangle(new System.Drawing.Pen(this.BackColor), x, y, 1, 5);
          
                else
          
                      g.DrawRectangle(new System.Drawing.Pen(Color.Red), x, y, 4, 4);
        

Pra que o formulário 1 pudesse passar as coordenadas para o formulário 2 e a partir delas pudesse executar a busca eu usei o delegate que é iniciado no evento Load do formulário 2 e aponta para o método de busca, como ilustra a listagem 05.

Listagem 05. Aponta o delegate para o método de busca.

            public bool Leitura(int x, int y)

    {

        Graphics grp = this.panel1.CreateGraphics();

        int _y;



        for(int l = this.panel1.Top;l<(this.panel1.Top + this.panel1.Height);l++)

        {

            _y = l-1;

            Application.DoEvents();



            Thread.Sleep(400);

            for(int c = this.panel1.Left; c < (this.Left + this.panel1.Width);c++)

            {

                Application.DoEvents();



                Desenha(grp, c, l, false);

                Desenha(grp, c-1, _y, true);

                                    

                if(l == y && c==x)

                {

                    MessageBox.Show("Ponto encontrado nas coordenadas x:" + c.ToString() + ", y:" + l.ToString());

                    return true;

                }

            }

    }

    return false;
}

private void Form2_Load(object sender, System.EventArgs e)

{

fDelegate = new delegateteste(Leitura);

}
        

Agora eu já posso chamar esse método no formulário 1 passando as coordenadas, assim como ilustra a listagem 06.

Listagem 06. Inicia a thread no evento click do botão que por sua vez chama o método scanniar.

            private void button1_Click(object sender, System.EventArgs e)

            {
            
                if(ValidarBrancos())
            
                  {                
            
                    Thread thr = new Thread(new ThreadStart(scanniar));
        
                    thr.Name = Thread.CurrentThread.GetHashCode().ToString();
        
                    thr.Start();
        
                  }
            
            }
            
            
            private void scanniar()
            
            {                
            
                  Application.DoEvents();
            
                  IAsyncResult ar = frm2.fDelegate.BeginInvoke(Convert.ToInt32(this.txtX.Text), Convert.ToInt32(this.txtY.Text), null, null);
                     
                  Application.DoEvents();
            
                  ar.AsyncWaitHandle.WaitOne(); 
            
                  bool termino = frm2.fDelegate.EndInvoke(ar);
            
                  if(!termino)
            
                        MessageBox.Show("Ops, coordenadas não encontradas !");
            
            }
        

O evento disparado no click do botão dispara uma thread que chama um método que executa o delegate do formulário 2.

É importante dizer que uma thread só pode disparar chamadas diretas para métodos sem parâmetros e sem retorno.

Apesar de ser um aplicativo sem muita utilidade o conceito utilizado nele pode ser aplicado em nosso dia-a-dia, um exemplo legal é quando temos que carregar formulários com muitas dropdownlist, datagrid e outros controles nós podemos ao invés de deixar o carregamento ser feito no load do formulário podemos carregar, por exemplo, durante a exibição de um formulário splash isso vai trazer muita agilidade a sua aplicação.