Quinta-feira, 02 de julho de 2009 às 10h30

Programação Assíncrona com Thread Pools

Faltam 0 dias! Inscreva-se agora! O maior encontro de profissionais web da américa latina.

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

Nenhum comentário até agora

Cancelar resposta

Qual a sua opinião?

Faça login abaixo ou cadastre-se rapidamente.


Sobre o Autor
José Carlos Macoratti é referência em Visual Basic no Brasil e autor dos livros "Aprenda Rápido: ASP" e "ASP, ADO e Banco de Dados na Internet". Mantenedor do site macoratti.net.

2001 - iMasters FFPA Informática Ltda - Todos os direitos reservados.