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 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 :
|
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: