VB.NET -  Trabalhando com Threads


 

Threads ,sequências ,  linhas de execução , encadeamento não importa o nome , todas as aplicações são executadas em uma thread.

 

Uma aplicação pode ter mais de uma thread ao mesmo tempo ou seja podemos estar fazendo várias coisas ao mesmo tempo , quer um exemplo? O sistema operacional Windows. A barra de tarefas exibe os processos que estão sendo executados simultaneamente.

 

Não se iluda , embora você esteja vendo a barra de tarefas (Task Manager) exibir diversos aplicativos sendo executados , nenhum deles esta sendo executado ao mesmo tempo. Não existe computador que possa realizar esta proeza com uma única CPU.


O que o Windows , e qualquer outro aplicativo que suporte a multitarefa , faz é estar alternando rapidamente entre diferentes threads de forma que cada thread pensa que esta executando independentemente , mas , só executa por algum tempo, interrompe e depois volta, e assim por diante.

 

Neste artigo eu vou abordar alguns conceitos sobre threads além dos já vistos em meu artigo anterior sobre o assunto: Trabalhando com MultiThreads no VB.NET

 

Sugiro pois que você leia o meu primeiro artigo para não ficar perdido no assunto:

 

Iniciando com Threads (revisão)

 

Para fazer uma breve revisão vamos criar uma aplicação no VB.NET onde teremos 3 threads sendo executadas , e onde poderemos ver alguns dos métodos e propriedades das threads revisadas.

 

Inicie um novo projeto no VS.NET e no formulário padrão insira 3 controles ListBox e botões de comando (Buttons) conforme a figura abaixo:

 

Por questão de simplicidade estou usando o nome padrão dos controles definido pelo VB.NET quando da sua criação.(Não recomendo esta atitude em projetos de produção.)

-Button1,Button2, ...,Button7

-ListBox1,ListBox2 e ListBox2


A primeira coisa a ter em mente é importa o namespace System.Threading no projeto e definir as threads que vamos usar no formulário. Como vamos usar 3 threads fazemos:


-no início do projeto

Imports System.Threading

- no início do formulário

Private t1, t2, t3 As Thread

 

O código que inicia cada thread esta associado ao evento click de cada botão: Abaixo temos o código para cada botão:

 

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        t1 = New Thread(AddressOf Me.prenchelista1)
        t1.Start()
End Sub
 Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
        t2 = New Thread(AddressOf Me.prenchelista2)
        t2.Start()
End Sub
 Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
        t3 = New Thread(AddressOf Me.prenchelista3)
        t3.Start()
 End Sub

 

O código exibido acima é igual para cada thread:

 

A primeira linha cria um instância do objeto thread(t1,t2 e t3) , o operador AddressOf é usado para criar um objeto delegate para a procedure preenchelista1, preenchelista2 e preenchelista3 ,  ou seja , ele aponta para o procedimento. Após isto basta executar a Thread usando o método Start() para cada thread.

 

O código que preenche cada listbox é exibido a seguir. Ele é muito parecido para cada listbox , com uma pequena diferença em cada caso:

 

    Public Sub prenchelista1()
        Dim j As Integer = 1
        While True
            ListBox1.Items.Add(" Thread 1 # " & CStr(j))
            j += 1
            Thread.CurrentThread.Sleep(1000)
        End While
    End Sub
------------------------------------------------------------------------   
    Public Sub prenchelista2()
        Dim k As Integer = 1
        While True
            ListBox2.Items.Add(" Thread 2 # " & CStr(k))
            k += 1
            Thread.CurrentThread.Sleep(2000)
        End While
    End Sub
----------------------------------------------------------------------   
    Public Sub prenchelista3()
        Dim m As Integer = 1
        While True
            ListBox3.Items.Add(" Thread 3 # " & CStr(m))
            m += 1
            If m = 10 Then
                ListBox3.Items.Add(" interrompi a thread...")
                Thread.CurrentThread.Sleep(Timeout.Infinite)
            End If
        End While
    End Sub

 

 

A primeira thread - t1 - irá preencher o listbox1 , mas com período de interrupção de 1000 milisegundos. Fazemos isto usando o método sleep() da thread.

 

t1.Sleep(n) - interrompe a thread imediatamente.   onde n é o valor em milisegundos da pausa.

 

A segunda thread - t2 - o período de interrupção é de 2000 milesegundos , para que possamos observar uma diferença na execução.

A terceira thread - t3 - estamos interrompendo a thread por um período indeterminado através do parâmetro : Timeout.Infinite.

Na segunda thread - t2 - estamos também usando os métodos : Suspend e Resume.

 

t2.Suspend() - Interrompe a thread mas não pára a thread imediatamente mas fica aguardando até que o runtime do Framework determine um ponto seguro onde a thread possa parar.

 

t2.Resume() - retoma a execução da thread que foi interrompida com Suspend().

 

Para parar uma thread de forma definitiva usamos o método abort().

 

Interagindo com procedimentos multi-thread

 

O que vimos acima é o básico sobre threads , mas não é muito funcional. Como faríamos para interagir com procedimentos executados por meio de threads ?

 

Você lembra do código que usamos para preencher as listbox ?

 

Para cada listbox usamos um código parecido criando assim três rotinas praticamente iguais. Como poderíamos criar uma rotina genérica que servisse para preencher tantas listbox quanto desejássemos ?

 

Para criar uma rotina genérica teríamos que passar parâmetros que seriam executados em cada thread específica.

 

Veja como podemos resolver este problema ...

 

Vamos então criar uma classe incluindo um no projeto um novo formulário através do menu Project opção Add Class. Vou dar o nome de macoratti.vb a este arquivo (você fique a vontade ...). Agora vamos criar uma classe com o código que irá preencher o controle listbox:

 

Public Class Macorati

    Public lstbox As ListBox
    Public id As Integer

    Public Sub prenchelista()
        Dim j As Integer = 1
        While True
            lstbox.Items.Add(" Thread " & id & " : " & CStr(j))
            j += 1
        End While
    End Sub

End Class
- Nesta classe estou definindo duas propriedades públicas

- lstbox do tipo ListBox
- id do tipo Integer

esta propriedades serão passadas como parâmetros para a classe preencher o listbox.

 

 

Vamos incluir agora outro formulário no projeto através do menu Project , opção Add Windows Forms.... Inclua um controle ListBox e dois botões de comando no formulário conforme layout abaixo:(Não esqueça de trocar

 

Não esqueça de incluir a cláusula Imports System.Threading no projeto e de definir a Thread no formulário :

Dim t As Thread

O código do botão Parar Thread é o seguinte :

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

        t.Abort()

    End Sub

 

No evento Click do botão - Disparar Thread - insira o código que irá passar os parâmetros para a classe que será executa na Thread:

 

  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim minhaClasse As New Macorati
        t = New Thread(AddressOf minhaClasse.prenchelista)
        minhaClasse.lstbox = ListBox1
        minhaClasse.id = 1
        t.Start()
    End Sub

 

Neste código estou instanciando um objeto da classe Macorati e a seguir usando o método preenchelista como endereço para a thread.

 

Após isto basta definir os parâmetros que eu desejo passar para a classe que será executada na Thread.

 

Sincronizando Threads

 

O método Join da classe Thread permite que você espere até que o procedimento executado pela Thread seja concluído. Abaixo temos os construtores para o método:

Overloads Public Sub Join()
Overloads Public Function Join(Integer) As Boolean
Overloads Public Function Join(TimeSpan) As Boolean

Se você usar o construtor sem parâmetro ele fará com que o programa pare até que a Thread termine a sua execução.

Se você passar um número inteiro como parâmetro indicando o tempo , a thread irá esperar o tempo em milisegundos definido , se a thread terminar antes o método retorna True caso contrário retorna False.

 

Temos também a declaração SyncLock ... End SyncLock que pode ser usada para bloquear um trecho de código durante a execução de uma thread; de forma que outras threads não tenham acesso áquele código enquanto a primeira thread estiver executando. Ela permite assim que múltiplas threads não executem o mesmo código ao mesmo tempo.

 

O exemplo retirado da MSDN é o seguinte :

 

Class Cache
   Private Shared Sub Add(ByVal x As Object)
      SyncLock GetType(Cache)
          ''codigo a ser bloqueado
      End SyncLock
   End Sub

   Private Shared Sub Remove(ByVal x As Object)
      SyncLock GetType(Cache)
         'codigo a ser bloqueado
      End SyncLock
   End Sub
End Class

 

 

Vamos a um exemplo onde teremos que calcular a área de um quadrado. Vamos criar uma thread que será sincronizada a fim de esperar até que o cálculo esteja completo para que o resultado seja retornado. Neste exemplo estaremos usando o método Join e SyncLock ...End SyncLock.

 

Vamos incluir um novo formulário - form3.vb - no projeto via menu Project | Add Windows Form . Usando o mesmo arquivo de classe que criei anteriormente - macoratti.vb - vamos criar uma classe para calcular a área de um quadrado:

 

Public Class cAreaQuadrado
    Public lado As Double
    Public Area As Double

    Public Sub CalculaArea()
        Area = lado * lado
    End Sub
End Class

 

Agora inclua um botão no formulário - form3.vb - e no evento click do mesmo coloque o código abaixo que irá

 

Private Sub Button1_Click(ByVal sender As System.Object,  ByVal e As System.EventArgs) Handles Button1.Click

    Dim oArea As New cAreaQuadrado()

    t = New Thread(AddressOf oArea.CalculaArea)
    oArea.Lado = 30
    t.Start()

End Sub

 

Observe o código acima e me responda uma pergunta: Como você vai obter o valor da área ? 

 

Você vai me responder : no campo oArea.Area , certo ???

 

Errado !!! Não podemos verificar o valor do campo oArea.Area após iniciar a Thread porque ele não conterá ainda o resultado, ele esta sendo feito em segundo plano e não temos como saber quando foi concluído.

 

Uma forma de resolver este impasse é gerar um evento para quando a thread estiver finalizada. Podemos então incluir na classe o evento que será gerado quando a mesma terminar. O código alterado já com o evento incluído ficará assim :

 

Public Class cAreaQuadrado

    Public lado As Double
    Public Area As Double

    Public Event ThreadCompleta(ByVal Area As Double)

    Public Sub CalculaArea()
        Area = lado * lado
        RaiseEvent ThreadCompleta(Area)
    End Sub
End Class

 

Agora no formulário form3.vb inclua o código no ínicio do formulário:

Dim WithEvents oArea As cAreaQuadrado

Altere o código do evento Click do botão de comando para :

 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        oArea = New cAreaQuadrado
        t = New Thread(AddressOf oArea.CalculaArea)
        oArea.lado = 30
        t.Start()

    End Sub

 


e crie a subrotina que irá exibir o resultado:

Sub AreaEventHandler(ByVal area As Double) Handles oArea.ThreadCompleta
    MsgBox("A área do quadrado é " & area)
End Sub

Executando o projeto você terá a área exibida em uma mensagem ao usuário.

 

Para sincronizar a thread usando Join e SyncLock/End SyncLock fazemos as seguinte alterações:

 

na classe cAreaQuadrado

 

   Public Sub CalculaArea()
        SyncLock GetType(cAreaQuadrado)
            Area = lado * lado
        End SyncLock
    End Sub


no código do botão de comando :

 

 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        oArea = New cAreaQuadrado
        t = New Thread(AddressOf oArea.CalculaArea)
        oArea.lado = 30
        t.Start()

        If t.Join(500) Then
            MsgBox(oArea.Area)
        End If
    End Sub

 

Executando o código o resultado será obtido usando o sincronismo entre as threads.

 

Valeu garoto,  se acompanhou até aqui já esta sabendo tudo sobre Threads...  então pegue o projeto aqui: Threads.zip (45k) .

 

ps: (Não esqueça de consultar as definições na MSDN on-line)

 

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

 

             Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter
 

Referências:


José Carlos Macoratti