ThreadPool: Programação com Multi-Threading
Introdução
Para compreender as multi tarefas, irei comparar com uma actividade prática. Imagine que tem uma máquina de café e dispõem de duas pessoas, se a maquina consegue tirar até quatro cafés em simultâneo, e uma fila de 20 pessoas esperando por café.
A maquina poderá funcionar sem parar até que todas as pessoas sejam servidas. Então será óbvio que cada pessoa se encarregue de tirar dois cafés em simultâneo, o que torna um processo mais rápido do que se fosse apenas uma pessoa a servir. Deverá ter em atenção que cada pessoa não poderá tirar mais do que dois cafés de cada vez.
Programando com multi-thread, a situação fica similar, podemos desenvolver várias tarefas em simultâneo, no entanto, este processo terá de ser controlado dependendo de dois principais factores: número máximo de tarefas que podem ser executadas em simultâneo e o número total de tarefas.
Compreender a mitologia é essencial para entender o conceito de execução de tarefas com multi-threads.
Desenvolvimento
ThreadPool.vb
Esta classe foi desenvolvida com o objectivo de controlar o número total de threads em execução em simultâneo.
'========================================
' Codificado por Carlos.DF
' fLaSh - 2010-01
' c4rl0s.pt@gmail.com
' carlosferreiracarlos@hotmail.com
'========================================
Imports System.Threading
Public Class ThreadPool
Inherits System.Object
Implements IDisposable
''' <summary>
''' Numerador para o estado geral dos Trabalhos
''' </summary>
''' Unknown: este estado não será a partida utilizado, mas melhor trabalhar por o suguro
''' <remarks></remarks>
Public Enum Status As Byte
Unknown = 0
Running = 1
Paused = 2
Canceled = 3
Ending = 4
Finished = 5
End Enum
' Envento da classe
Public Event OnFinished()
' Declarações locais
Private __MaxThreads As Integer
Private __RunningThreads As Integer
Private __ActiveThreads As List(Of Thread)
Private __Status As Status
Private __AllPushed As Boolean
Private __IsPaused As Boolean
''' <summary>
''' Construtor da classe
''' </summary>
''' <remarks></remarks>
Public Sub New(ByVal MaxThread As Integer)
If MaxThread <= 0 Then
Throw New Exception("ThreadPool:New - O número maximo de Threads tem de ser superior a 0.")
End If
__MaxThreads = MaxThread
__ActiveThreads = New List(Of Thread)
End Sub
''' <summary>
''' Retorna o total de threads a correr em simultaneo..
''' </summary>
''' <remarks></remarks>
Public ReadOnly Property RunningThreads() As Integer
Get
Return __RunningThreads
End Get
End Property
''' <summary>
''' Retorna o numero maximo de threads que podem correr em simultaneo..
''' </summary>
''' <remarks></remarks>
Public ReadOnly Property MaxThread() As Integer
Get
Return __MaxThreads
End Get
End Property
''' <summary>
''' Retorna o estado actual da classe
''' </summary>
''' <remarks></remarks>
Public ReadOnly Property ThreadsStatus() As Status
Get
Return __Status
End Get
End Property
''' <summary>
''' Simple metodo para pause ou resumir o trabalho..
''' </summary>
''' <remarks>No case de Pause=True, entrara em pause no proximo thread..</remarks>
Public Property Pause() As Boolean
Get
Return __IsPaused
End Get
Set(ByVal value As Boolean)
__IsPaused = value
End Set
End Property
''' <summary>
''' Abre novo thread e icrementa o total de threads activos
''' </summary>
''' <param name="oThread">Opcional o objecto do thread</param>
''' <remarks></remarks>
Public Sub Open(Optional ByVal oThread As Thread = Nothing)
SyncLock Me
If __AllPushed Then Return
Interlocked.Increment(__RunningThreads)
If oThread IsNot Nothing Then __ActiveThreads.Add(oThread)
__Status = Status.Running
__IsPaused = False
End SyncLock
End Sub
''' <summary>
''' Fecha um thread e decrementa o total de threads activos
''' </summary>
''' <param name="oThread">Opcional o objecto do thread</param>
''' <remarks></remarks>
Public Sub Close(Optional ByVal oThread As Thread = Nothing)
SyncLock Me
Interlocked.Decrement(__RunningThreads)
If oThread IsNot Nothing Then __ActiveThreads.Remove(oThread)
' Defeni o status da classe
If __AllPushed Then
If __RunningThreads > 0 Then
__Status = Status.Ending
Else
__Status = Status.Finished
' Corre o envento final..
RaiseEvent OnFinished()
End If
End If
End SyncLock
End Sub
''' <summary>
''' Informa a Classe que não existe mais trabalhos em lista..
''' </summary>
''' <remarks>Este Sub é importante, terá de ser chamado no final do processo dos trabalhos,
''' para dar a aconhecer que terminou os trabalhos..</remarks>
Public Sub AllJobsPushed()
SyncLock Me
Me.__AllPushed = True
Me.__IsPaused = False
End SyncLock
End Sub
''' <summary>
''' Termina tudo o processo..
''' </summary>
''' <remarks></remarks>
Public Sub Abort()
SyncLock Me
__Status = Status.Canceled
Me.AllJobsPushed()
For Each oThread As Thread In __ActiveThreads.ToArray()
Try
Me.Close(oThread)
oThread.Abort()
'Interlocked.Decrement(__RunningThreads)
Catch ex As Exception
Debug.WriteLine("Erro ao cancelar um thread: " & ex.Message)
End Try
Next
End SyncLock
End Sub
''' <summary>
''' Espera por o thread para proximo trabalho, caso ja esteja o numero maximo de threads em simultaneo..
''' </summary>
''' <remarks></remarks>
Public Sub WaitForThreads()
'Cria um cliclo infinito..
Do
' Sincroniza a classe
SyncLock Me
' Verifica uma das duas opções..
Select Case True
' Simples, caso os threads a correr seja maior que o numero maximo de threads,
' então termina o ciclo para dar lugar ao inicio de novo thread, ou no caso de o processo
' estar a terminar 'Ending' ou 'Finished'..
Case ((__RunningThreads < __MaxThreads) Or _
((__Status = Status.Ending) Or (__Status = Status.Finished)) Or (__Status = Status.Canceled)) And _
(Not __IsPaused)
Exit Do
Case Else
' Ou então, pausa o processo e continua o ciclo infinito..
Threading.Thread.Sleep(100)
End Select
End SyncLock
Loop
End Sub
''' <summary>
''' Espera que todos os thread activos terminem o seu trabalho..
''' </summary>
''' <remarks></remarks>
Public Sub WaitForThreadsDone()
'Cria um cliclo infinito..
Do
' Sincroniza a classe
SyncLock Me
' Verifica uma das duas opções..
Select Case True
' Simples, caso os threads a correr seja maior que o numero maximo de threads,
' então termina o ciclo para dar lugar ao inicio de novo thread..
Case (__RunningThreads = 0) Or (__Status = Status.Finished) And _
(Not __IsPaused)
Exit Do
Case Else
' Ou então, pausa o processo e continua o ciclo infinito..
Threading.Thread.Sleep(100)
End Select
End SyncLock
Loop
End Sub
#Region " Suporte para a Interface IDisposable "
Private __IsDisposed As Boolean
Protected Overridable Sub Dispose(ByVal bDisposing As Boolean)
If Not Me.__IsDisposed Then
If bDisposing Then
If Not Me.__Status = Status.Finished Then
Me.Abort()
End If
End If
End If
Me.__IsDisposed = True
End Sub
''' <summary>
''' Este código foi adicionado pelo Visual Basic para implementar corretamente o padrão 'Dispose'.
''' </summary>
''' <remarks>Não altere este código. Coloque no código do Sub Dispose (ByVal bDisposing As Boolean) acima.</remarks>
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
Exemplo prático
Crie um novo projecto do tipo Console Application, adicione a classe ThreadPool.vb
indicada a cima, e coloque este código no modulo que o VS irá adicionar.
Imports System.Threading
Module mMain
' Objecto para o controle dos threads em execução em simultaneo..
Private __ThreadPool As ThreadPool
Public Sub Main()
System.Console.WriteLine("Prima qualquer tecla para inicar o teste.")
System.Console.ReadKey()
'Inicia o teste
Call IniciaTrabalho()
System.Console.WriteLine("Prima qualquer tecla para sair.")
System.Console.ReadKey()
End Sub
Private Sub IniciaTrabalho()
Const TOTAL_TAREFAS As Integer = 100
Const MAX_THREADS As Integer = 20
' Escreve na janela
System.Console.WriteLine(New String("*", 80))
System.Console.WriteLine( _
String.Format("**Lista de trabalhos iniciada, Max. threads em simultaneo: {0}, total de tarefas {1}", MAX_THREADS, TOTAL_TAREFAS) _
)
' Inicia a instancia da classe para o controle dos threads em execução em simultaneo..
__ThreadPool = New ThreadPool(MAX_THREADS)
' Percorre todas as listas de tarefeas..
For iLoop As Integer = 1 To TOTAL_TAREFAS
' Inicia novo thread..
Dim oThread As New Thread(AddressOf FazTarefa)
oThread.IsBackground = True
oThread.Name = "Pos: " & iLoop.ToString
' Cria a instancia da classe usada por o thread..
Dim oJob As New ThreadObj(oThread, iLoop)
' Inicia thread..
oThread.Start(oJob)
' Abre o Thread..
__ThreadPool.Open(oThread)
' Aguarda por uma slot livre.. caso necessario..
__ThreadPool.WaitForThreads()
Next
' Inidica que todos os 'trabalhos' foram completos..
__ThreadPool.AllJobsPushed()
' Espera que o threads activos terminem o seu trabalho..
'Aqui poderia utilizar desta forma.. mas como prentende saber os threads que faltam terminar irei usar outro metodo..
'__ThreadPool.WaitForThreadsDone()
' No entanto, para perceber melhor o seu conseito, irei controlar manualmente os threads que ainda
'não terminaram a sua tarefa..
Do
' Reporta o progresso
Threading.Thread.Sleep(1000)
' Escreve na janela Debug do VS
System.Console.WriteLine( _
String.Format("A terminar os trabalhos, threads activos: {0}", __ThreadPool.RunningThreads) _
)
Loop While __ThreadPool.RunningThreads > 0
' Escreve na janela
System.Console.WriteLine(New String("*", 80))
System.Console.WriteLine( _
"**Lista de trabalhos concluida" _
)
End Sub
Private Sub FazTarefa(ByVal t As ThreadObj)
' Cria uma pause a aleatoriamente ( 1 ~ 5 segundo )
Dim oRandom As New Random()
Dim iSleep As Integer = oRandom.Next(1000, 5000)
' Faz a pause
Thread.Sleep(iSleep)
' Fecha o thread (IMPORTANTE)
__ThreadPool.Close(t.Thread)
' Escreve na janela Debug do VS
System.Console.WriteLine( _
String.Format(" Trabalho {0} terminou tarefa, total threads em execução {1}", t.ID, __ThreadPool.RunningThreads) _
)
End Sub
''' <summary>
''' Esta classe serve como contentor de codigo do objecto da tarefa
''' Deverá a estender como pretender..
''' </summary>
''' <remarks></remarks>
Private Class ThreadObj
Private __Thread As Threading.Thread
Private __ID As Integer
Public Sub New(ByVal t As Threading.Thread, ByVal id As Integer)
__Thread = t
__ID = id
End Sub
Public ReadOnly Property Thread() As Threading.Thread
Get
Return __Thread
End Get
End Property
Public ReadOnly Property ID() As Integer
Get
Return __ID
End Get
End Property
End Class
End Module
Conclusão
O conceito multi-tread é sem dúvida o futuro do desenvolvimento de aplicações, actualmente os processadores actuais são constituídos com mais do que um core, assim podemos tirar o máximo proveito dos mesmos, criando aplicações mais rápidas e com mais performance!