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

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:

Synchronize(Self.avancarProgressBar);

Poderíamos muito bem ter feito desta:

    Synchronize(
      procedure ()
      begin
        Self.FPgbProgresso.StepBy(1);
      end
    );

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:

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:

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

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:

procedure TfrmPrincipal.inicializarAplicacao;
begin
  apagarLogAntigoComThread();
  criarArquivosTemporarios();
  if conectarAoBancoDeDados() then
  .
  .
  .
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:

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

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:

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

Sobre Diego Garcia

Analista/Desenvolvedor Delphi desde 2008, bacharel em Ciência da Computação e entusiasta de metodologias ágeis e engenharia de software.
Esse post foi publicado em Programação e marcado , . Guardar link permanente.

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

  1. Ribeiro disse:

    Cara, seus postos são deveras esclarecedores. Sou principiante em programação e mesmo assim não foi difícil o entendimento da matéria. Um grande abraço! Vê se manda mais alguns ai. Valeuuu!!!

  2. ChrisSoares disse:

    Diego, gostei muito da sua matéria. Estava estudando sobre as Threads e imaginando que seria muito trabalhoso criar um objeto para cada processo que desejamos controlar em paralelo. Mas esta sua explicação sobre as Threads Anonimas foi um alívio.
    Gostaria de explorar o seu conhecimento sobre as Threads, se possível. Ainda continuo estudando elas e estou com uma situação. Minha aplicação começa a realizar várias consultas a bancos de dados e atualizações, o que acaba sendo demorado. Gostaria de deixar um componente TAnimate se movendo para que o usuário não tenha a impressão que o sistema esta travado, mas assim que começam as consultas a animação para e so volta depois que finaliza.
    Como eu poderia utilizar uma Thread para deixar o formulário com esta animação em execução enquanto que o processo de atualizações continua ?

    Obrigado, Deus continue a lhe abençoar!

    • Diego Garcia disse:

      Opa amigo, tudo bom? muito obrigado pelo feedback, realmente, para processos simples as threads anonimas são uma mão na roda.
      Eu acredito que o TAnimate não roda como thread, pois ele atualiza a VCL, ai para atualizar a VCL tem que ser na MainThread. Você pode tentar fazer o contrário, fazer as suas consultas a banco dentro de uma thread. Ai antes de iniciar a thread você mostra seu form com o TAnimate e no OnTerminate da thread você fecha o form.
      Me diga se funciona, qualquer coisa, podemos pensar em outra solução.

      []’s

      • Lucas disse:

        Diego, tudo bem?
        Eu estava com o mesmo problema da ChrisSoares, utilizei o Synchronize para habilitar e desabilitar um TAniIndicator no código e funcionou corretamente, não sei se é o correto, mas funcionou. Espero ter ajudado.

        TThread.Synchronize (TThread.CurrentThread,procedure ()
        begin
        AniIndicator1.Visible := True;
        end);
        //código…
        TThread.Synchronize (TThread.CurrentThread,procedure ()
        begin
        AniIndicator1.Visible := False;
        end);

  3. Ótimo post, camarada.
    Não conhecia Threads anônimas e sua perfeita explicação solucionou meu problema no fechamento das minhas aplicações feitas pra Android e iOS, utilizando o Delphi.

  4. Junior disse:

    Tem como vc me enviar os demos deste artigo? e-mail: farnetani@gmail.com

Deixe um comentário