Software">
Nothing Special   »   [go: up one dir, main page]

Threads No Delphi

Fazer download em docx, pdf ou txt
Fazer download em docx, pdf ou txt
Você está na página 1de 13

Threads no Delphi, por onde começar ?

– Parte I
Publicado em 31/01/2013 por Diego Garcia
Thread é um termo conhecido por qualquer “informata” que se preze, mas para quem não se recorda,
qualquer aplicação utiliza no minimo uma thread (a thread principal ou main thread) onde o fluxo do
processamento é executado, nessas aplicações os comandos são executados um por vez de forma
sequencial. Até ai nada demais, mas a coisa passa a ficar interessante quando usamos mais de uma
thread em uma aplicação, fazendo com que seja possível executar processamentos de forma paralela.
No dia a dia usamos as threads para gerenciar tarefas que precisam ser executadas paralelamente,
melhorar desempenho de processamentos, etc. Mas não se iluda, usar threads é simples, mas requer
alguns cuidados.
Deixando a teoria e o blablabla de lado, vamos ver um pouco sobre como funcionam as threads no
Delphi.
Começaremos criando um exemplo bem básico, faremos um método que escreve de 1 a N linhas em um
arquivo de texto tendo o intervalo de 1 milissegundo a cada linha escrita, o nome deste arquivo sempre
será hora, minuto, segundo e milésimo de criação e o numero de linhas escritas, ex:  14-30-01-
200_total_linhas_1500.txt. Inicialmente criaremos esse método da forma comum e depois passaremos
para uma thread, assim conseguiremos realizar algumas comparações no resultado final. Para a
execução comum, faremos dessa forma:

1 procedure escreverArquivoDeTexto(const dirArquivo: string;


2   linhasAImprimir: integer);
3 var
4   linhasArquivoTexto : TStringList;
5   I: Integer;
6 begin
7   linhasArquivoTexto := TStringList.Create;
8   try
9     for I := 1 to linhasAImprimir do
10     begin
11       linhasArquivoTexto.Add(format('Linha numero %d',[i]));
12       Sleep(1);
13     end;
    linhasArquivoTexto.SaveToFile(Format('%s\%s_total_linhas_%d.txt',
14
[dirArquivo,FormatDateTime('hh-mm-ss-zzz',Now),linhasAImprimir]));
15   finally
16     linhasArquivoTexto.Free;
17   end;
18 end;

Para testar esse método, faremos com que ele seja executado 3 vezes e analisaremos a diferença de
tempo de gravação entre um e outro, lembrando que, como ainda não estamos trabalhando com
threads, será escrito um arquivo por vez.

1 escreverArquivoDeTexto(ExtractFileDir(ParamStr(0)),10500);
2 escreverArquivoDeTexto(ExtractFileDir(ParamStr(0)),10200);
3 escreverArquivoDeTexto(ExtractFileDir(ParamStr(0)),10400);

Usei um número grande de linhas para que fosse possível medir o desempenho. Ao executar a
sequência acima, tive os seguintes arquivos criados no diretório do executável de teste:

1 14-47-22-711_total_linhas_10500.txt
2 14-47-33-078_total_linhas_10200.txt
3 14-47-43-661_total_linhas_10400.txt
Sendo assim, podemos concluir que levou cerca de 10 segundos de diferença para cada arquivo.
Faremos agora novamente essa rotina, porém, faremos com que ela seja executada dentro de uma
thread.
Para facilitar nossas vidas, o Delphi já possui o esqueleto padrão para a criação de uma thread, basta ir
em FILE -> NEW -> OTHER -> THREAD OBJECT, após selecionar Thread Object será solicitado o nome da
classe da sua Thread (para nosso exemplo utilizei TArquivoTextoInThread) e pronto, teremos o seguinte
código já montado em uma nova unit:

1 type
2   TArquivoTextoInThread = class(TThread)
3   private
4     { Private declarations }
5   protected
6     procedure Execute; override;
7   end;

REPORT THIS AD

Antes de voltarmos a falar sobre conceitos, vamos primeiro preparar a nossa classe para que ela escreva
em um arquivo de texto. Sendo rápido e prático, criaremos dois atributos privados na classe, um
referente ao número de linhas a serem escritas e outro referente ao diretório onde o arquivo será
criado. Feito isso, passaremos a nossa rotina de criar o arquivo de texto para dentro desta classe,
ficando desta forma:

1 type
2   TArquivoTextoInThread = class(TThread)
3   private
4     FLinhasAImprimir : integer;
5     FDirArquivo      : String;
6     procedure escreverArquivoDeTexto();
7     function gerarNomeArquivo():string;
8   protected
9     procedure Execute; override;
10   public
    constructor Create (const CreateSuspended : boolean; const linhasAImprimir :
11
integer; const dirArquivo : string);
12   end;

Certo, eu fiz algumas coisas a mais, mas vamos por partes, primeiro, vamos ver como ficou nosso
método escreverArquivoDeTexto().

1 procedure TArquivoTextoInThread.escreverArquivoDeTexto;
2 var
3   linhasArquivoTexto : TStringList;
4   I: Integer;
5 begin
6   linhasArquivoTexto := TStringList.Create;
7   try
8     for I := 1 to Self.FLinhasAImprimir do
9     begin
10       linhasArquivoTexto.Add(format('Linha numero %d',[i]));
11       Sleep(1);
12     end;
13     linhasArquivoTexto.SaveToFile(Self.gerarNomeArquivo());
14   finally
15     linhasArquivoTexto.Free;
16   end;
17 end;

REPORT THIS AD

Basicamente nada mudou, só para melhorar a legibilidade do código foi criado um método para gerar o
nome do arquivo de texto, porém ele tem a mesma lógica utilizada anteriormente:

1 function TArquivoTextoInThread.gerarNomeArquivo: string;


2 begin
  result := Format('%s\%s_total_linhas_%d.txt',[Self.FDirArquivo,FormatDateTime('hh-
3
mm-ss-zzz',Now),self.FLinhasAImprimir]);
4 end;

Sendo assim, até aqui nenhuma novidade, mas agora vamos para um pouco de conceitos analisando o
construtor que fiz para a classe:

1 constructor TArquivoTextoInThread.Create(const CreateSuspended: boolean;


2   const linhasAImprimir: integer; const dirArquivo : string);
3 begin
4   Self.FLinhasAImprimir := linhasAImprimir;
5   Self.FDirArquivo      := dirArquivo;
6   Self.FreeOnTerminate  := true;
7   inherited Create(CreateSuspended);
8 end;

A classe TThread possui uma propriedade booleana chamada FreeOnTerminate  que quando alterada
para true a thread é liberada automaticamente da memória após a sua execução. Outra coisa que é
importante de notar é o parâmetro CreateSuspended  que o construtor da classe TThread recebe, ele
basicamente define se a thread será criada suspensa ou não, ou seja, se a thread será executada
automaticamente após a instância ser criada (CreateSuspended = false) ou se será necessário iniciar a
execução da thread de forma manual (CreateSuspended = true) através do método Start. Para completar
a implementação da nossa classe, vamos ver como ficou o método Execute() da thread:

1 procedure TArquivoTextoInThread.Execute;
2 begin
3   escreverArquivoDeTexto();
4 end;

REPORT THIS AD

Ok, imagino que você esperava mais do que simplesmente uma chamada para o
método escreverArquivoDeTexto() porém, o segredo todo está ai, o método Execute()  é exatamente o
método que será executado assim que a thread for iniciada. Com isso, a nossa thread está pronta. Agora
vamos fazer o mesmo teste que fizemos anteriormente mas dessa vez utilizando nossa thread:

1 var
2   oArquivoTextoInThread1 : TArquivoTextoInThread;
3   oArquivoTextoInThread2 : TArquivoTextoInThread;
4   oArquivoTextoInThread3 : TArquivoTextoInThread;
5 begin
  oArquivoTextoInThread1 :=
6
TArquivoTextoInThread.Create(false,10500,ExtractFilePath(ParamStr(0)));
  oArquivoTextoInThread2 :=
7
TArquivoTextoInThread.Create(false,10200,ExtractFilePath(ParamStr(0)));
  oArquivoTextoInThread3 :=
8
TArquivoTextoInThread.Create(false,10400,ExtractFilePath(ParamStr(0)));
9 end;

Executando a rotina acima, como determinei que minha thread não irá iniciar suspensa, tive os
seguintes arquivos criados no diretório do executável:
1 15-42-46-946_total_linhas_10200
2 15-42-47-167_total_linhas_10400
3 15-42-47-256_total_linhas_10500

Note que o último arquivo a ser criado foi o de 10500 linhas mesmo sendo o primeiro a ter sua rotina
executada e outra coisa muito importante é a diferença de tempo na criação dos arquivos, se antes sem
thread tivemos uma demora de 10 segundos para cada arquivo, aqui não passou de poucos milésimos,
tivemos esse resultado pois cada arquivo demora cerca de 10 segundos para ser gerado, porém, como
os 3 estavam sendo gerados ao mesmo tempo de forma paralela, não foi necessário utilizar uma fila de
processamento.

Como o intuito deste post foi só demonstrar o uso de threads, não entrei em mais detalhes, porém, na
próxima parte veremos um pouco mais sobre como controlar o ciclo de vida de uma thread.

Threads no Delphi, por onde começar ? – Parte II


Publicado em 08/02/2013 por Diego Garcia

Dando continuidade à esse estudo introdutório, veremos mais sobre como manipular uma thread. Na
primeira parte vimos como criar uma thread e a importância do método Execute(). Vejamos agora
outros métodos e propriedades da classe TThread:
 Start(): Basicamente inicia a execução de uma thread, este método é necessário quando
criamos uma thread definindo o seu estado inicial como Suspended  (suspensa), por
exemplo:

1 oMinhaThread := TMinhaThread.Create(true); //instanciada como suspensa


2 oMinhaThread.Start();

 Terminate(): Utilizado para parar a execução de uma thread, basicamente este método seta
a variável Terminated da thread para true, assim sendo, é necessário realizar o controle
desse status na rotina executada na thread, por exemplo:

Acionando o fim da thread


1 oMinhaThread.Terminate();

 Realizando o controle de término dentro da thread


1 procedure TMinhaThread.Execute;
2 begin
3   while (not Self.processamentoFinalizado()) do
4   begin
5     if Self.Terminated then //verifica se foi solicitado o encerramento da thread
6     begin
7       Self.definirProcessamentoFinalizado();
8       break;
9     end;
10     Self.realizarProcessamento();
11   end;
12 end;

 Suspend(): Este método pausa a execução da thread suspendendo a sua execução. Mas
atenção!!! Este método se tornou obsoleto (deprecated) a partir do Delphi XE:

1 oMinhaThread.Suspend();

 Resume(): Ao contrário do Suspend(), o Resume() retoma a execução de uma thread que foi


pausada. Assim como o Suspend() o Resume() também se tornou obsoleto:

1 oMinhaThread.Resume();

 OnTerminate: O OnTerminate é na realidade uma propriedade do tipo TNotifyEvent. Assim


que a thread é encerrada o OnTerminate será executado, tornando possível atribuir um
evento para ser executado assim que a thread chegar ao fim. Esse método será executado
no contexto da thread principal e não no contexto da thread atual (veja mais sobre multi-
thread no próximo post):

1 procedure TForm1.reportarFimThread(Sender: TObject);


2 begin
3   //O Sender é sempre a thread que está sendo finalizada
  gravarLog(format('[%s] Fim da Thread de ID: %d',
4
[TimeToStr(now),TThread(Sender).ThreadID]));
5 end;

 Atribuindo a procedure para o OnTerminate da thread

1 oMinhaThread := TMinhaThread.Create(true);
2 oMinhaThread.OnTerminate := reportarFimThread;

 WaitFor(): Este método aguarda a finalização da thread (finalização do método Execute()) e


retorna o ReturnValue (um Integer que pode ser definido no método SetReturnValue()) da
thread. O uso do WaitFor() é recomendado para liberar a thread da memória com
segurança:
1 oMinhaThread.Terminate();
2 if not oMinhaThread.Finished then
3   oMinhaThread.WaitFor();
4 oMinhaThread.free;

REPORT THIS AD

Essas são as mais básicas e também as principais características da classe TThread. Além


das características citadas neste post, para quem não leu a primeira parte deste estudo, vale relembrar
do Execute() que é o método executado assim que a thread é iniciada e do FreeOnTerminate que,
quando setado para true, faz com que a thread seja liberada da memória automaticamente ao chegar
no fim da sua execução.
Na próxima parte vamos ver uma pouco sobre como criar blocos protegidos para acessar objetos
compartilhados entre threads com uma maior segurança.

Threads no Delphi, por onde começar ? – Parte III


Publicado em 11/02/2013 por Diego Garcia

Então vamos lá, ver um pouco mais sobre as Threads no Delphi. Nessa terceira parte, veremos como
criar blocos protegidos em uma thread para, entre outras coisas, interagir com a GUI (Graphical User
Interface) com uma maior segurança. Com o intuito de simplificar o estudo, faremos o já clássico
exemplo das barras de progressão sendo manipuladas paralelamente.
Basicamente nosso exemplo será uma thread que receberá o ponteiro de um TProgressBar. Essa thread
irá setar a posição do TProgressBar em zero e depois fará um laço onde o TProgressBar será preenchido
até a sua posição final (propriedade max). Seguindo a linha de raciocínio da primeira parte do estudo,
vamos criar uma nova thread com a seguinte estrutura:

1 type
2   TProcInThread = class(TThread)
3   private
4     FPgbProgresso : TProgressBar;
5     procedure atualizarProgressBar;
6     procedure avancarProgressBar;
7   public
8     constructor Create(CreateSuspended : boolean; pgbProgresso : TProgressBar);
9   protected
10     procedure Execute; override;
11   end;

Seguindo a mesma lógica dos posts anteriores, nosso construtor não apresenta nenhuma novidade:
constructor TProcInThread.Create(CreateSuspended : boolean;pgbProgresso:
1
TProgressBar);
2 begin
3   Self.FPgbProgresso := pgbProgresso;
4   inherited Create(CreateSuspended);
5 end;

O método avançarProgressBar() foi feito mais por uma questão de conveniência, porém, entrarei em


maiores detalhes em um próximo post:

1 procedure TProcInThread.avancarProgressBar;
2 begin
3   Self.FPgbProgresso.StepBy(1);
4 end;

O método Execute() também não possui nada demais, somente a chamada para o


método atualizarProgressBar(), onde toda nossa lógica vai estar:

1 procedure TProcInThread.Execute;
2 begin
3   atualizarProgressBar();
4 end;

Agora sim, vamos ver o método mais importante da nossa thread, o atualizarProgressBar():

1 procedure TProcInThread.atualizarProgressBar;
2 var
3   I: Integer;
4 begin
5   Self.FPgbProgresso.Position := 0;
6   for I := 0 to Self.FPgbProgresso.Max - 1 do
7   begin
8     if Self.Terminated then
9       abort();
10     Synchronize(Self.avancarProgressBar);
11     Sleep(100);
12   end;
13 end;

Coloquei o Sleep(100) para que fosse possível visualizar melhor a execução, fora isso, chegamos
finalmente no X da questão, o método Synchronize(). Este método executa a chamada de um
determinado método (passado como parâmetro) dentro da thread principal da aplicação (main thread),
ou seja, seguindo o nosso exemplo, o comando:

1 Synchronize(Self.avancarProgressBar);
Faz com que o método avancarProgressBar() seja executado dentro da thread principal da aplicação,
fazendo com que o procedimento do método seja protegido e não entre em conflito com outras threads
que manipulem os mesmos dados que a thread atual.
Com isso, nossa thread já está pronta. Para vê-la em execução, vamos criar uma formulário com 3
TProgressBar e 2 TButtons. No private do formulário, crie 3 objetos do tipo TProcInThread que é a classe
da nossa thread:

1 type
2   TForm1 = class(TForm)
3     ProgressBar1: TProgressBar;
4     ProgressBar2: TProgressBar;
5     ProgressBar3: TProgressBar;
6     Button1: TButton;
7     Button2: TButton;
8   private
9     oProcInThread1 : TProcInThread;
10     oProcInThread2 : TProcInThread;
11     oProcInThread3 : TProcInThread;
12   public
13   end;

Sete a propriedade Max dos TProgressBar em 50 e no Button1 faremos a iniciação das threads:

1 procedure TForm1.Button1Click(Sender: TObject);


2 begin
3   oProcInThread1 := TProcInThread.Create(true,ProgressBar1);
4   oProcInThread2 := TProcInThread.Create(true,ProgressBar2);
5   oProcInThread3 := TProcInThread.Create(false,ProgressBar3);
6  
7   oProcInThread1.Start;
8  
9   oProcInThread2.FreeOnTerminate := true;
10   oProcInThread2.Start;
11 end;

Note que a terceira thread (oProcInThread3) não foi criada como Suspended, sendo assim não é
necessário chamar o método Start() para iniciar a execução da thread e a segunda thread
(oProcInTread2) foi configurada para ser liberada da memória assim que termine sua execução.
Agora em nosso Button2, liberaremos as threads da memória, mas somente as que não são liberadas
automaticamente:

1 if not oProcInThreadBarra1.Finished then


2   oProcInThreadBarra1.WaitFor();
3 FreeAndNil(oProcInThreadBarra1);
4  
5 if not oProcInThreadBarra3.Finished then
6   oProcInThreadBarra3.WaitFor();
7 FreeAndNil(oProcInThreadBarra3);

Finalmente, basta executar o aplicativo e ver o resultado:


O uso de blocos protegidos em um ambiente multi-thread é uma questão muito delicada, não proteger
seus blocos que possuam acesso a dados compartilhados (como por exemplo um objeto, uma tabela ou
um componente visual) abre margem para a ocorrência de conflitos, enquanto que o ato de proteger
muitos blocos (utilizando o synchronize() por exemplo) pode comprometer o desempenho da sua
aplicação. As boas práticas recomendam que evitem ao máximo compartilhar recursos entre as threads
e nos casos que não for possível, existem algumas técnicas para se fazer.
Eu particularmente não recomendo o uso do synchronize()  se a intenção não seja interagir com
componentes visuais, caso seja necessário proteger um bloco crítico existem outras opções como por
exemplo o TCriticalSection ou o TMutex que serão temas para posts futuros.
Na próxima etapa do estudo, veremos um pouco sobre AnonymousThread  e métodos anônimos.

Threads no Delphi, por onde começar ? – Parte IV


Publicado em 13/02/2013 por Diego Garcia

Quem leu a terceira parte deste estudo deve se lembrar quando mencionei:


“O método  avançarProgressBar()  foi feito mais por uma questão de  conveniência, porém, entrarei em
maiores detalhes em um próximo post”
Esse método, avançarProgressBar() foi utilizado no synchronize da thread, fazendo com que sua
execução fosse direcionada para a thread principal da aplicação, porém, o
método avançarProgressBar()  era extremamente simples e de certa forma desnecessário (ok, estou
ignorando algumas boas práticas de código limpo, em nome da didática), digo isto por que
o synchronize aceita como parâmetro não só um ponteiro para um método (como fizemos no post
passado), mas também um método anônimo, ou como é conhecido, Anonymous Method. Não entrarei
em muitos detalhes, mas resumidamente um método anônimo é um método sem nome que pode ser
escrito e passado como um parâmetro de uma função, ou seja, se no post anterior fizemos desta forma:

1 Synchronize(Self.avancarProgressBar);

Poderíamos muito bem ter feito desta:

1 Synchronize(
2   procedure ()
3   begin
4     Self.FPgbProgresso.StepBy(1);
5   end
6 );

Realmente, a primeira impressão não é a das melhores, mas ainda sim pode ser algo muito útil. Vale a
pena se aprofundar um pouco mais nos estudos sobre Anonymous Method principalmente no que diz
respeito ao seu acesso e escopo de variáveis em comparação aos ponteiros para métodos, porém, esse
não é o foco deste post, na verdade, mostrei a existência de métodos anônimos para poder falar sobre
as Threads Anônimas ou Anonymous Threads.
Imagine um cenário em que você precise realizar um processamento relativamente pesado, porém não
faz sentido bloquear o sistema para o usuário até que esse processamento seja concluído, já que
teoricamente esse processamento não é de interesse do usuário, como por exemplo, limpar todos os
arquivos de log mais antigos de um sistema. Qual seria sua primeira sugestão? Tenho certeza que seria
“criar uma thread”. Mas pense no trabalho de criar uma thread (criar uma classe descendente de
TThread, implementar o método execute, etc.). Se o propósito da thread for muito simples, podemos
criar uma thread anônima. Mas como fazemos isso?

A classe TThread possui o método estático CreateAnonymousThread() com a seguinte assinatura:

1 class function TThread.CreateAnonymousThread(const ThreadProc: TProc): TThread;

Esse método retorna um objeto do tipo TThread já instanciado. O parâmetro ThreadProc recebe um


método anônimo que entre outras palavras, será o método executado por nossa thread, ou seja, ele
será o execute() da thread anônima. Por padrão, as threads anônimas são criadas suspensas
(Suspended) e com a propriedade FreeOnTerminate como true. Com essas informações, vamos colocar
em prática a solução para o cenário comentado anteriormente, a limpeza dos logs antigos:

1 procedure TfrmPrincipal.apagarLogAntigoComThread;
2 begin
3   TThread.CreateAnonymousThread(
4     procedure()
5     var
6       SearchRec : TSearchRec;
7       i : integer;
8       sDir : string;
9     begin
1
      sDir := ExtractFileDir(ParamStr(0));
0
1
      i := FindFirst(sDir + '\*.log',0,SearchRec);
1
1
      while i = 0 do
2
1
      begin
3
1         if StrToInt(Copy(SearchRec.Name,1,4)) < (strtoint(FormatDateTime('yyyy',Now))
4 - 1) then
1
          deleteFile(sDir + '\' +SearchRec.Name);
5
1
        i := FindNext(searchRec);
6
1
      end;
7
1
    end
8
1
  ).Start;
9
2
end;
0

Inicialmente é um pouco complicado para ler o código, por isso caprichei na  indentação. Note que não
foi necessário atribuir a thread anônima a um objeto e repare que já estou iniciando a sua execução
invocando o método Start(). Como disse anteriormente, essa thread será executada e liberada da
memória automaticamente.
A nossa thread basicamente procura na pasta do executável da aplicação, todos os arquivos com a
extensão .log  e depois (levando em consideração que o padrão do nome dos arquivos de log seja algo
como ‘AAAA-MM-DD.log’ por exemplo), deleta todos que forem do ano retrasado para trás, por
exemplo, se estamos em 2013, todos arquivos que começarem com 2011, 2010, 2009…. serão
deletados. Imaginando que essa thread seria executada na inicialização de um
sistema, poderíamos fazer desta forma por exemplo:

1 procedure TfrmPrincipal.inicializarAplicacao;
2 begin
3   apagarLogAntigoComThread();
4   criarArquivosTemporarios();
5   if conectarAoBancoDeDados() then
6   .
7   .
8   .
9 end;

Mesmo que tivesse mais de 365 arquivos de log antigos, o processamento seguiria paralelo às outras
rotinas de inicialização, tornando o processo como um todo mais rápido. Caso fosse necessário atualizar
um componente visual de dentro da thread anônima, poderíamos usar o synchronize, mas como
estamos lidando com uma thread anônima, a forma de chamar o synchronize é um pouco diferente.
Vamos imaginar que nossa thread de limpar log, após varrer o diretório, escreva em um statusBar
indicando que o processamento foi encerrado, teríamos algo assim:

1 procedure TfrmPrincipal.ApagarLogAntigoComThread;
2 begin
3   TThread.CreateAnonymousThread(
4     procedure()
5     var
6       SearchRec : TSearchRec;
7       i : integer;
8       sDir : string;
9     begin
1
      sDir := ExtractFileDir(ParamStr(0));
0
1
      i := FindFirst(sDir + '\*.log',0,SearchRec);
1
1
      while i = 0 do
2
1
      begin
3
1         if StrToInt(Copy(SearchRec.Name,1,4)) < (strtoint(FormatDateTime('yyyy',Now))
4 - 1) then
1
          deleteFile(sDir + '\' +SearchRec.Name);
5
1
        i := FindNext(searchRec);
6
1
      end;
7
1
      //atualizar componente vcl
8
1
      TThread.Synchronize(TThread.CurrentThread,
9
2
        procedure
0
2
        begin
1
2           frmPrincipal.stbRodape.SimpleText := 'Arquivos de log antigos deletados com
2 sucesso';
2
        end
3
2
      );
4
2
    end
5
2
  ).Start;
6
2
end;
7

Veja que agora é necessário informar qual thread está invocando o synchronize. Como não atribuímos
explicitamente a instância da thread anônima para um objeto, podemos usar o
método TThread.CurrentThread(). Caso tivéssemos atribuído a thread para um objeto, bastaria passar
esse objeto para o método sychronize:

1 TThread.Synchronize(oThreadAnonima,frmPrincipal.atualizarStatusBar('Log processado'));

Eu particularmente não recomendo de forma alguma o uso de threads anônimas para rotinas
complexas, mas, sem dúvidas que as Anonymous Thread facilitam nossas vidas, já que podemos realizar
processamentos pesados porém simples de forma multi-thread, sem a necessidade de criar uma classe
especifica de thread para isso.
Na próxima parte, veremos algumas dicas para realizar o debug de nossas threads. Até lá.

Threads no Delphi, por onde começar ? – Parte V


Publicado em 18/02/2013 por Diego Garcia

Chegamos ao final desse estudo introdutório sobre o fantástico mundo do processamento concorrente,
proporcionado pelo uso das threads. Nessa quinta e última parte, veremos algumas dicas sobre como
não enlouquecer ao realizar debugging em um ambiente multi thread. Você pode até perguntar “mas
qual é o real problema do debbuing em um ambiente multi thread?”. Imagine a seguinte situação, você
possui uma thread que está em execução contínua (monitorando um diretório por exemplo), enquanto
esta thread é executada, você inicia a depuração de outra rotina qualquer executada pela main thread.
De repente, sem a menor explicação, o ponteiro do seu debug vai para outra rotina em outra unit sem a
menor relação com a depuração que você estava fazendo. Quando você consegue se dar conta do que
aconteceu, percebe que o ponteiro da depuração foi movido para o processamento da thread. Quem
está familiarizado com um ambiente multi thread conhece bem esse tipo de história.

Para começar, vamos conhecer a janela Thread Status (View->Debug Windows->Threads ou Ctrl + Alt +


T)

Essa janela mostra todas as threads da aplicação, assim como seu estado, status, etc. Por padrão, ao
entrar em modo de debugging o próprio Delphi já abre esta janela no rodapé da tela. Esta imagem foi
capturada de uma aplicação onde, além da main thread, eu tinha mais 3 threads em execução. Ainda
assim, mesmo com a janela thread status  aberta, não ajuda muito, pois não sei qual thread é qual. Para
resolver esse problema, podemos identificar as threads em debugging através do
método NameThreadForDebugging() da classe TThread. Esse método pode ser utilizado no execute da
thread:

1 procedure TArquivoTextoInThread.Execute;
2 begin
3   Self.NameThreadForDebugging(Format('arquivo txt %d',[self.FLinhasAImprimir]));
4   Self.escreverArquivoDeTexto();
5 end;

Inclusive é possível nomear a main thread, basta utilizar um cast com a classe TThread. Por exemplo:

1 procedure TfrmPrincipal.FormCreate(Sender: TObject);


2 begin
3   TThread(Self).NameThreadForDebugging('main thread');
4 end;
Com as threads devidamente nomeadas, a janela threads status passa a ficar muito mais clara:

Caso você consiga identificar uma thread pelo id, você pode alterar seu nome para debugging na janela
thread status Basta clicar com o botão direito do mouse sobre a linha da thread e depois selecionar a
opção name thread.
Agora, imagine que você queira depurar uma determinada thread, mas não quer que a execução de
outra thread qualquer possa interferir nesse processo. Em uma situação destas você pode parar a
execução de uma ou todas as threads. Quando a aplicação tem a sua execução pausada (caindo em um
breakpoint ou caso o botão de pause seja clicado), você pode clicar com o botão direito do mouse sobre
a thread que deseja pausar a execução e selecionar a opção Freeze, ou pode clicar com o botão direito
do mouse sobre a única thread que você deseja que continue executando e se selecionar a opção Freeze
All Other Threads.

Veja que estou com o ponteiro de depuração em um breakpoint de uma thread (status  Breakpoint) e
parei a execução de outra thread (status Frozen). Caso eu passe a depurar a thread (com o F8 ou F7 por
exemplo) o status mudaria de Breakpoint para Stepped. Para liberar a execução da thread congelada
(Frozen), basta clicar nela e selecionar a opção Thaw.
Por falar em breakpoints, você pode condicionar um breakpoint para que ele funcione somente quando
aquele trecho de código seja executado por uma determinada thread. Para realizar essa configuração,
clique com o botão direito do mouse sobre o breakpoint e selecione a opção Breakpoint Properties, uma
das propriedades é exatamente a thread:
Isso garante que o breakpoint só será acionado, caso a thread corrente seja a thread configurada.

Espero que tenha ajudando um pouco com essas dicas, apesar de threads não serem nenhuma
novidade, vejo que muitas vezes é um assunto que não recebe o seu devido valor. Obviamente o
assunto não se encerra aqui, até mesmo porque esse estudo recebeu o nome de Threads no Delphi, por
onde começar? Sendo assim, isso é só o começo.

Você também pode gostar