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: