Quando você está desenvolvendo uma aplicação, pode
se deparar com uma situação onde existe a necessidade de realizar um
processamento mais intenso. Isso pode fazer com que sua aplicação sofra uma perda de
desempenho dando a sensação ao usuário que a aplicação está ‘congelada’.
Se você não precisa aguardar o término deste
processamento para que o usuário interaja com a aplicação, pode usar o recurso do
ThreadPool para obter um comportamento assíncrono sem ter que recorrer ao
processamento de MultiThreads.
MultiThreads??? Meu Deus!!! O que é isto???? Calma!! O
termo MultiThread define a capacidade de se executar múltiplos
processos ao mesmo tempo de forma independente.
Ou seja, você executa duas tarefas (assobiar e chupar cana) ao mesmo
tempo e uma tarefa não depende da outra para ser executada.
Vamos dar um exemplo para clarear: suponha que você queira carregar um controle ListView com os dados de
uma tabela e que, ao mesmo tempo, queira também preencher o conteúdo de uma
Combobox com os dados de outra tabela. Bem, se conseguir realizar tal
proeza ao mesmo tempo e de forma independente, você esta realizando um
processamento MultiThread.
Ora, ora, você pode estar pensando com
os seus botões que o VB6.0 suporta o processamento MultiThread… Mas eu lhe digo que está enganado. Na verdade, quando você
tenta fazer tal tarefa usando o VB 6.0, a tarefa é feita sequencialmente; só
que a coisa é tão rápida (se a tabela tiver poucos registros) que
parece – eu disse parece – que as tarefas são realizadas simultaneamente. O
que o VB6.0 faz é executar múltiplos apartamentos dentro de um único
processo.
No VB.NET a multiTarefa (MultiThread)
é uma realidade, ou seja, podemos ter múltiplos processos paralelos que
podem acessar o mesmo conjunto de dados compartilhados.
A plataforma .NET mantém um pool de
threads prontas para serem usadas, que são idéias para realizar
tarefas rápidas. Geralmente essas threads são usadas para
operações assíncronas de acesso a arquivos ou operações
realizadas pela chamada de um Delegate ou BeginInvoke.
Delegates são tipos usados para invocar um ou mais métodos onde o método atual
invocado é determinado em tempo de execução.
Delegates provê uma
forma de invocar método pelo seu endereço ao invés de seu nome.
Como estas tarefas são rápidas, a
criação e destruição das threads passa a ter uma porção
significativa no tempo de execução da tarefa. Para evitar que
gerenciamento de linhas de execução (Threads) afete o
desempenho, a .NET Framework cria um pool de threads quando
necessário até um valor limite e, então, mantém as threads do
pool em estado de espera e prontas para a próxima operação
assíncrona. Com isso, é ocupada apenas uma pequena quantidade de
memória para cada thread, otimizando o desempenho.
A classe
System.Threading.ThreadPool fornece um número de métodos
estáticos que permitem que você monitore e controle as threads
do pool.
Vejamos os mais importantes:
- GetMaxThreads – Retorna o número
máximo das threads ativas no pool. - GetAvailableThreads- Retorna a
diferença entre o número máximo de threads do pool e o número
atual de threads ativas. - GetMinThreads – Retorna o número
de threads ociosas que o pool mantém e espera de novas
requisições. - SetMinThreads – Altera o valor
mínimo de threads disponíveis no pool. Se você diminuir
muito o número de threads ociosas pode afetar o desempenho do
sistema; - SetMaxThreads – Altera o valor
máximo de threads disponíveis no pool; - QueueUserWorkItem – Enfileira um
método para execução. O método será executado quando uma
thread do pool estiver disponível. Geralmente você usa este
método para executar um processo em outra thread. Pode ser usado
da seguinte forma:
| QueueUserWorkItem(WaitCallBack) | Enfileira um método para execução onde o método executa quando a thread estiver ativa. |
| QueueUserWorkItem(WaitCallBack, Object) |
Enfileira um método para execução e especifica um objeto contendo os dados para ser usado pelo método. O método executa quando a thread estiver disponível. |
Para exemplificar a utilização destes métodos, vamos
usar o Visual Basic 2008 Express Edition para criar
uma aplicação do tipo Console com o nome de
threadPool_1 digitando o código abaixo:
Imports System.Threading
Module Module1
Sub Main()
' exibe o estado padrão das threads do pool
exibeEstadoThreads() '
' Altera os parâmetros do Pool de threads
Console.WriteLine("Alterando o numero de Threads...")
If Not ThreadPool.SetMaxThreads(100, 500) Then
Console.WriteLine("Chamada a SetMaxThreads falhou....")
End If
If Not ThreadPool.SetMinThreads(25, 25) Then
Console.WriteLine("Chamada a SetMinThreads falhou....")
End If
' Inicia uma thread do pool
ThreadPool.QueueUserWorkItem(AddressOf NaofazNada)
Thread.Sleep(10)
' Mostra o novo estado da thread
exibeEstadoThreads()
' Aguarda para encerrar o programa
Console.WriteLine()
Console.WriteLine("Pressione Enter para encerrar...")
Console.ReadLine()
End Sub
Sub exibeEstadoThreads()
' Retorna o número máximo de threads do pool
Dim threadsAtivas As Integer
Dim threadsTerminadas As Integer
ThreadPool.GetMaxThreads(threadsAtivas, threadsTerminadas)
Console.WriteLine("Máximo de threads ativas={0}" & vbCrLf &
"Máximo de I/O threads={1}", threadsAtivas, threadsTerminadas)
' Retorna o no mínimo de threads ociosas
ThreadPool.GetMinThreads(threadsAtivas, threadsTerminadas)
Console.WriteLine("Minino de threads ativas={0}" & vbCrLf &
"Minimo de I/O threads={1}", threadsAtivas, threadsTerminadas)
' Mostra threads disponíveis
ThreadPool.GetAvailableThreads(threadsAtivas, threadsTerminadas)
Console.WriteLine("Threads ativas disponíveis={0}" & vbCrLf &
"Threads I/O disponíveis={1}", threadsAtivas, threadsTerminadas)
End Sub
Sub NaofazNada(ByVal state As Object)
Thread.Sleep(1000)
Console.WriteLine("Sem fazer nada...")
End Sub
End Module
Executando este projeto, iremos obter o seguinte
resultado:

Usando ThreadPool em
uma aplicação Windows Forms
Como já foi mencionado, a classe ThreadPool do namespace
System.Threading contém um pool virtual de threads disponíveis que você pode
usar. Você pode usar a classe ThreadPool para realizar operações assíncronas com
recurso de Multithread sem ter que escrever código para gerenciar o pool de
threads.
A classe ThreadPool gerencia um grupo
dinâmico de threads que estão disponíveis para realizar o processamento
multithread. O número de threads no pool é dinâmico
e você não é obrigado realizar nenhum trabalho extra para gerenciar as threads
no pool. Se você requisitar uma thread e não existir nenhuma disponível, o
pool pode criar uma nova thread ou esperar até que uma thread esteja disponível.
O gerenciamento das threads é feita pelo pool de
forma transparente e você não tem que se preocupar com esse detalhe apenas com o
seu código.
Para demonstrar como usar o recurso ThreadPool
e obter o mesmo resultado obtido pela construção de threads vamos propor um
problema que simula uma aplicação Windows com um grande processamento inicial.
Para forçar uma simulação, a aplicação Windows irá carregar 100 milhões de números
em um controle ListBox.
Se a aplicação simplesmente tentar carregar os
números inteiros no controle ListBox no evento Load do formulário, ele irá demorar vários minutos para ser exibido. Mas se executarmos esta
operação em uma thread separada, então o nosso formulário irá exibir o controle
ListBox de imediato.
Nota: Observe que podemos
usar este recurso pois não precisamos que o processamento de carregar os números
na LIstBox esteja concluído para poder exibir a
ListBox e aguardar a interação
do usuário.
Usando o recurso ThreadPool, o formulário será
carregado de imediato enquanto a LIstBox continua a ser carregada em segundo
plano ficando disponível para a interação com o usuário.
Abra o Visual Basix 2008
Express Edition e crie um novo projeto do tipo
Windows Forms Application com o nome threadPool_2
;
Inclua no formulário padrão
form1.vb um controle ListBox e um botão de comando Button
conforme o leiaute abaixo:

Defina o namespace Imports
System.Threading para termos acesso a classe ThreadPool.
No evento Load do formulário vamos incluir o
código:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
ListBox1.Items.Clear()
ThreadPool.QueueUserWorkItem(AddressOf carregaLista)
End Sub
Estamos usando o método QueueUserWorkItem da
classe ThreadPool para enfileirar a execução da rotina carregaLista
quando uma thread do pool estiver disponível (este método é estático
– Shared – e por isso não precisamos criar uma instância da classe ThreadPool).
Vejamos agora o código da rotina carregaLista():
Private Sub carregaLista(ByVal state As Object)
Dim i As Double
SyncLock ListBox1.GetType
For i = 10000000000000000 To 1 Step -1
item = i
ListBox1.Invoke(CType(AddressOf incluir, MethodInvoker))
Next
End SyncLock
End Sub
Esta rotina irá efetivamente carregar o controle
ListBox com milhões de números. Observe que o código está sendo executado no
interior de um SyncLock.
A instrução
SyncLock garante que múltiplas
threads não executem as instruções do bloco ao mesmo tempo e previne que
cada thread inicie a execução do código no bloco até que a outra
thread terminou de executar o mesmo o código.
Resta agora exibir o código da rotina incluir
:
Private Sub incluir()
ListBox1.Items.Add(item)
Application.DoEvents()
End Sub
Neste código estamos incluindo os valores no
controle ListBox.
O método Application.DoEvents permite que seu
aplicativo manipule outros eventos que podem ser disparados enquanto seu código
é executado.
O método
My.Application.DoEvents possui o mesmo
comportamento que o método DoEvents.
Executando o projeto teremos a exibição do
formulário com o controle ListBox já preenchido e a rotina para preenchimento
continua sendo executada em outra thread.
Observe que usamos a thread a partir do pool
através da classe ThreadPool de forma mais simples e sem preocupação em
gerenciar threads.
Uma palavra final sobre Threads em
aplicações ASP .NET. Com certeza podemos também usar o recurso das
threads nas aplicações ASP .NET para por exemplo executar processos que
envolvem grandes processamentos no servidor. Como o assunto merece um tratamento à parte irei publicar um artigo a respeito em breve.
Eu sei, é apenas VB .NET, mas eu
gosto…




