sexta-feira, 10 de junho de 2016

Linguagem C# - Trabalhando com Threads em Windows Forms


Todo desenvolvedor já se deparou em uma situação que precisou fazer o uso de “Threads”. Antes de partirmos para a parte prática gostaria de discorrer um pouco delas.

Explicando um pouquinho

As denominadas “Threads” significam “linhas de execuções”, seria uma forma de um processo dividir a si mesmo em duas ou mais tarefas que podem ser executadas concorrentemente. (Fonte: https://pt.wikipedia.org/)
Um bom exemplo disto seria o próprio Sistema Operacional, o mesmo trabalha com as denominadas Multi-threads, onde se divide as linhas de execução aumentando sua eficiência. Poderemos imaginar uma “Thread” como um trecho de código que é executado em paralelo ao seu código, sendo que podem existir diversas threads executando em um dado momento. A partir do momento que você cria uma thread, o Sistema Operacional fica sabendo que, além de tudo que já está fazendo, há mais código que precisa ser executado. Se existem mais threads do que processadores/núcleos (que é o caso mais visto nos computadores pessoais e dispositivos móveis), então o Sistema Operacional começa a "agendar um momento" para que cada thread execute em um determinado núcleo/processador (processo conhecido como escalonamento, ou agendamento).

Como Funciona no C#?

É importante lembrar que todo programa desenvolvido em C# possui uma thread, denominada “Thread Principal”. Quando montamos rotinas que demandam um tempo maior e se a thread principal for dedicada a isto, o programa pode parar de responder até que sua execução seja concluída. (Por exemplo, uma rotina que utiliza um laço para ler 1.000.000 de registros e a partir dele alimentar uma Barra de Progresso). Neste caso para permitir que este mesmo programa execute esta tarefa e continue a responder normalmente deveremos implementar o uso de “Threads”. Realizando o processamento em segundo plano melhoramos o desempenho das tarefas do processador consequentemente reduzindo o tempo que leva para completar esta tarefa.

Conhecendo a Classe “Thread”

Neste tópico irei explicar algumas das principais funcionalidades da classe Thread. Importante lembrar que todas estas informações foram baseadas na versão 4.5 e 4.5 do .Net Framework. A classe Thread respeita a hierarquia abaixo:

Sytem.Object
    
System.Runtime.ConstrainedExecution.CriticalFinalizerObject
          System.Threading.Thread

     
Possuindo outras classes bases para sua implementação.

Sintaxe

A sintaxe poderá ser conferida no código abaixo:

ComVisibleAttribute(true)]
ClassInterfaceAttribute(ClassInterfaceType.None)]
public sealed class Thread : CriticalFinalizerObject, _Thread

Principais comandos (Construtores, Propriedades e Métodos)

Antes de partirmos para a parte prática, achei de extrema importância apontar os principais comandos quando fazemos o uso de “Threads”. Começando pelo Construtor seguindo pelas suas propriedades e finalizando com seus Métodos.

Conhecendo os Construtores

1) Thread(ParameterizedThreadStart)
Teremos como inicialização uma nova instância da classe Thread, especificando um delegate (o mesmo permite que um objeto seja passado para o segmento quando este for iniciado.)

2) Thread(ParameterizedThreadStart, Int32)
Possui as mesmas características citadas acima, adicionando como segundo parâmetro o tamanho máximo de pilha para o segmento.

3) Thread(ThreadStart)
Apenas inicia uma nova instância para a classe.

Inicia uma nova instância especificando o tamanho máximo de pilha para o segmento.

Conhecendo as Propriedades

1) CurrentContext
Esta propriedade obtém o contexto atual em que o segmento está sendo executado.

2) CurrentCulture
Tem como objetivo obter ou definir a contura para o segmento atual.

3) CurrentPrincipal
Obtém ou define o principal atual da thread (para segurança baseada em função).

4) CurrentThread
Esta propriedade nos informa o thread em execução no momento.

5) ExecutionContext
Retorna um objeto que contém todas informações sobre os diferentes contextos de segmentos que estão sendo executados no momento.

6) IsAlive
Valor do status de execução atual.

7) IsBackground
Propriedade que indica se o segmento é um segmento de plano de fundo.

8) IsThreadPoolThread
Obtém um valor indicando se um segmento pertence ao thread pool gerenciado.

9) ManagedThreadId
Obtém um identificador exclusivo para o segmento gerenciado atual.

10) Name
Definição ou obtenção de um nome do segmento.

11) Priority
Obtém ou define um valor que indica a prioridade de programação de um segmento.

12) ThreadState
Obtém um valor que contém os estados de segmento atual.

Conhecendo os Métodos

1) Abort()
Método para encerrar a Thread.

2) AllocateDataSlot
Este método atribui um slot sem nome de dados em todos os segmentos. Para melhor desempenho, campos de uso marcados com o atributo de ThreadStaticAttribute em vez disso.

3) BeginThreadAffinity
tem como objetivo notificar um host que o código gerenciado está prestes a executar instruções que dependem da identidade do segmento físico atual do Sistema Operacional.

4) EndThreadAffinity
Tem a finalidade de notificar um host que o código gerenciado terminou de executar instruções que dependem da identidade do segmento físico atual do sistema operacional.

5) GetData
Este método recupera o valor do slot especificado no segmento atual, dentro do domínio atual da thread atual. Para melhor desempenho, campos de uso marcados com o atributo de “ThreadStaticAttribute em vez disso.

6) Join()
Bloquear o segmento de chamada até que um segmento termina, para continuar a executar com padrão e o bombeamento de “SendMessage”.

7) ResetAbort()
Abortar o aplicativo para o segmento atual.

8) SetData
Definir os dados no slot especificado no thread em execução no momento, para o domínio atual do segmento. Para melhor desempenho, use os campos marcados com o atributo de “ThreadStaticAttribute em vez disso.

9) Sleep(int32)
Suspende o segmento atual por um período especificado.

10) Start()
Faz com que o sistema operacional modifique o estado da instância atual a “ThreadState.Running.

1) Criando um exemplo prático via console

No primeiro momento iremos demonstrar como devemos proceder para criar uma Thread via console. Para isto clique “File/New/Project...” escolhendo “Console Application”. Ver Imagem 01.

Figura 01: Thread via console.

Este projeto irá possuir apenas uma classe principal chamada “ThreadExemploConsole”, a que será executada quando compilarmos e rodarmos o programa. No próprio exemplo abaixo irei descrevendo todos os passos. Primeiramente deveremos importar algumas bibliotecas para esta tarefa. Ver Listagem 01.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;


namespace ConsoleApplicationThreads
{
    public class ThreadExemploConsole
    {

    public static void ThreadProc()
    {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("ThreadProc: {0}", i);
                Thread.Sleep(10);
            }
    }

Dentro da classe pública e estática iremos criar um método estático chamado “ThreadProc()”, produziremos a Thread secundária a partir dele. Faremos um Loop e escreveremos na tela a cada iteração realizada.

     public static void Main()
    {
Console.WriteLine("Thread principal: Iniciando a segunda Thread.");

Thread t = new Thread(new ThreadStart(ThreadProc));

t.Start();
          Thread.Sleep(20);
          for (int i = 0; i < 4; i++)
          {
Console.WriteLine("Thread Principal: Executando...");
               Thread.Sleep(0);
          }

Console.WriteLine("Thread principal : Método Join() esperando até a segunda Thread terminar.");
          t.Join();
Console.WriteLine("Thread principal : Segunda Thread finalizou. Pressione Enter para finalizar programa.");
          Console.ReadLine();
        }

A partir do construtor da classe Thread, passaremos como parâmetro a thread secundária seguido do método “ThreadStart” (para iniciá-la). Lembrando que este parâmetro é um “delegate”, necessário para utilizar o método “ThreadProc”. Logo após usaremos os métodos: Start() e Sleep(), respectivamente para iniciar a tread principal fazendo com que a thread secundária aguarde o término da mesma. Teremos uma noção clara do funcionamento analisando o resultado obtido através da Imagem 02.


    }
}
Listagem 01.

Figura 02: Resultado - Saída de Dados.

2) Criando um exemplo prático via Windows Forms

A ideia deste exemplo seria de demonstrar de uma forma simples o uso de “Threads” em tarefas que podemos nos deparar no dia-a-dia. Quem nunca precisou criar uma rotina para leitura de dados de uma tabela por exemplo? Geralmente quando precisamos realizar iterações com uma grande quantidade de elementos é imprescindível possuir uma interface amigável para o usuário, na maioria das vezes podemos implementar o uso de uma barra de progresso junto com as “Threads”, permitindo executar mais de um processo por vez, como foi explicado no exemplo anterior. Com aplicações Windows Forms precisaremos implementar o uso de Delegates, para assim podermos executar as Threads Secundárias. Para isto adicione no formulário um “Button” e um “ProgressBar”. Ver Figura 03.

Figura 03: Trabalhando com Threads em Windows Forms.

Abaixo a codificação necessária, Ver Listagem 02

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Threading;
using System.Reflection;

Primeiramente importaremos algumas bibliotecas necessárias.

namespace WindowsFormsApplicationThreads
{
    public partial class FrmThreads : Form
    {
        public FrmThreads()
        {
            InitializeComponent();
        }

private void btnIniciar_Click(object sender, EventArgs e)
        {
Thread thread = new Thread(new       ThreadStart(ExecutarLoop));
              thread.IsBackground = true;
          thread.Start();
        }

No botão “Iniciar” é onde faremos a chamada da “Thread”. Primeiramente passaremos como parâmetro o método “ExecutarLoop” invocando o método “ThreadStart”. A propriedade “IsBackground” especifica a thread como plano de fundo. O método “Start()” irá inicializá-la.
.
        public void ExecutarLoop()
        {
              int total = 100000;
          int incrementaValor = 0;

SetControlPropertyValue(progressBar1, "value", 1);
SetControlPropertyValue(progressBar1, "minimum", 1);
SetControlPropertyValue(progressBar1, "maximum", total);

            /* Implementando Threads  */
           
            while (incrementaValor < total)
            {
incrementaValor = progressBar1.Value + 1;
SetControlPropertyValue(progressBar1,           "value", incrementaValor);
            }
        }

Este método fará um laço para preencher uma barra de progresso. Não podemos esquecer que trabalhar com threads junto com windows forms deveremos fazer o uso de um “delegate” (Para maiores informações sobre este asssunto recomendo a leitura do artigo do mês de Maio de 2015 chamado “Linguagem C# - Delegates em Windows Forms”)

A solução abaixo foi encontrada através do seguinte site: http://shabdar.org/c-sharp.html e comentada também em http://www.macoratti.net

delegate void SetControlValueCallback(Control oControl, string propName, object propValue);

Iremos declarar um delegate responsável por atualizar valores da Thread Secundária.

private void SetControlPropertyValue(Control oControl, string propName, object propValue)
       {
            if (oControl.InvokeRequired)
            {
SetControlValueCallback d = new SetControlValueCallback(SetControlPropertyValue);
oControl.Invoke(d, new object[] { oControl, propName, propValue });
            }
            else
            {
                Type t = oControl.GetType();
                PropertyInfo[] props = t.GetProperties();
                foreach (PropertyInfo p in props)
                {
if (p.Name.ToUpper() == propName.ToUpper())
                    {
p.SetValue(oControl, propValue, null);
                    }
                }
            }
        }
    }
}
Listagem 02.

Este método “SetControlPropertyValue” terá como parâmetro de entrada o tipo de controle, a propriedade e o valor.

Podemos conferir o resultado na Figura 04.

Figura 04: Exemplo em Execução.

Conclusões

O uso de “Threads” se torna uma prática muito utilizada no desenvolvimento de programas. Determinadas tarefas que encontramos o uso deste mecanismo se torna indispensável para aumentar a qualidade do código e do resultado final para o usuário.
Com este artigo procurei demonstrar os principais construtores, métodos e propriedades da classe “Thread” junto com exemplos simples que os ajudarão a adaptar para praticamente todas as necessidades.

Um forte abraço e até o mês que vem!

Referências

https://msdn.microsoft.com/pt-br/library/system.threading.thread%28v=vs.110%29.aspx

Nenhum comentário:

Postar um comentário