VB .NET - Programação Assíncrona com Thread Pools
Quando você esta desenvolvendo uma aplicação pode se deparar com uma situação onde existe a necessidade de realizar um processamento mais intenso que pode fazer com que sua aplicação sofra uma perda de desempenho dando a sensação ao usuário que a aplicação esta '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 esta 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ápido (se a tabela tiver poucos registros) que parece , eu disse parece, que as tarefas são realizadas simultâneamente.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 ter um 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 o formulário 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.Threadingpara 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() |
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 esta 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) |
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 a parte irei publicar um artigo a respeito em breve.
Eu sei é apenas VB .NET, mas eu gosto...
referências:
José Carlos Macoratti