Demonstrando maneiras diferentes de usar tópicos em Delphi

Muitos usuários de Delphi cometem o erro de pensar que um tópico é algum tipo de mágica que melhorará o desempenho de seu aplicativo. Infelizmente, isso está longe de ser verdade. O maior erro nº 1 ao tentar implementar um thread é fazê -lo acessar diretamente os controles visuais do aplicativo. Mas esses controles visuais só podem funcionar no contexto do encadeamento principal do aplicativo. O uso de outro thread para atualizar os controles na interface do usuário deve ser planejado e implementado com muito cuidado. E na maioria dos casos, provavelmente não é a solução certa para o problema.
Simplificando, a estrutura VCL de Delphi não é segura. Embora existam muitas maneiras de integrar um thread à sua interface do usuário, não existe uma solução única. Sempre varia dependendo do que você está tentando realizar. Na maioria das vezes, as pessoas querem realizar um melhor desempenho (velocidade). Mas isso é muito raramente feito usando um tópico. Em vez disso, os cenários mais comuns em que um thread é integrado a uma interface de usuário é manter essa interface do usuário responsiva durante uma longa tarefa.
Para isso, imaginaremos um aplicativo simples com apenas um botão que baixará um arquivo da Internet quando clicado. O aplicativo já possui um thread principal usado para toda a interface do usuário. Na plataforma do Windows, isso significa enviar/receber mensagens do Windows, desenhar para uma tela de controle, reconhecer a interação do usuário etc. Este thread é essencialmente um loop gigante que está girando muito rápido. Para cada revolução deste fio giratório, certas peças de código são executadas.
Em um único ambiente encadeado, esse download de arquivo bloquearia esse loop da spinning, até que o download seja concluído. Durante esse período, este thread não é mais capaz de fazer atualizações da interface do usuário, detectar cliques do usuário ou qualquer coisa. É isso que faz o Windows colocar (não responder) no título de tais formas, porque, bem, assim como diz, não está respondendo.
É aqui que entram threads adicionais. Ele precisa responder ao Windows. Em vez de bloquear o tópico principal da interface do usuário com este download de arquivo gigante, você pode colocar esse arquivo download em outro thread. É tão simples, certo?
Você pode se perguntar "como faço para monitorar o progresso?" ou "Como faço para ser notificado quando terminar?" Isso significaria que o tópico de download precisa de alguma forma interagir com o thread principal. É exatamente onde entra a confusão. Um thread não pode simplesmente interferir em outro thread, porque não há como dizer em que ponto um thread está realmente. Agora há dois loops separados e, quando você deseja atualizar a interface do usuário, esse tópico da interface do usuário pode estar em qualquer lugar fazendo qualquer coisa. Mais importante, suponha que o encadeamento principal da interface do usuário esteja no processo de escrever uma string na mesma propriedade de controle para a qual seu outro thread também deseja escrever. Agora você tem dois threads tentando escrever no mesmo endereço de memória, o que pode resultar em problemas imprevisíveis.
Sincronizando. A classe TTHread da Delphi possui um método synchronize () que permite que um thread interaja com o encadeamento principal da interface do usuário apenas em um momento em que ele realmente se comportará corretamente, quando realmente espera tal ocorrência. O código sincronizado a partir de outro thread não é executado no contexto desse thread - ele sempre é executado no contexto do encadeamento principal da interface do usuário. Essa é a idéia de synchronize (), é executar o código no thread da interface do usuário.
Então, no final, você não usa o VCL do thread. Em vez disso, seu thread envia um sinal para o encadeamento principal e somente quando o thread principal estiver pronto, ele executará esse código. Enquanto isso, seu fio secundário é bloqueado enquanto aguarda o thread principal terminar.
Depois, há o erro de pensar que uma grande operação da interface do usuário seria melhor em um tópico. Digamos que você tenha uma lista em que deseja preencher milhões de itens. É claro que isso levará tempo e, durante esse período, seu aplicativo não responderá. De novo. Então, basta mover esse código para um tópico, certo?
Novamente, qualquer interação da interface do usuário deve ser feita a partir do encadeamento principal e apenas do encadeamento principal. Os threads são úteis se você precisar executar cálculos longos, processar quantidades maciças de dados, aguardar uma resposta de um recurso remoto ou qualquer outra coisa que consome tempo e não esteja diretamente relacionada à interface do usuário.
É difícil dizer. Mas há uma prática comum que é altamente aconselhada ao escrever um tópico: coloque seu código de thread em uma unidade própria. Esta unidade deve ser isolada de qualquer outra unidade da interface do usuário. Ele nem deve ter nenhuma unidade relacionada ao VCL em sua cláusula de uso. O tópico nem deveria saber como está sendo usado. Deve ser essencialmente um manequim, com o único objetivo de executar sua longa tarefa. Quando se trata de atualizações de interface do usuário de um thread, isso é melhor realizado por eventos sincronizados.
Exatamente o que parece. É um evento sincronizado, conforme explicado anteriormente. Um evento é simplesmente um ponteiro para um procedimento que você pode atribuir ao thread antes de iniciar. Dentro do thread, quando você precisar atualizar a interface do usuário, usaria o Synchronize () para acionar este evento. Com esse design, o tópico nunca precisaria saber que está sendo usado por uma interface do usuário. Ao mesmo tempo, você também inadvertidamente realiza abstração. O tópico se torna reutilizável. Você pode conectá -lo a outro projeto que pode nem ter uma interface de usuário (digamos um serviço do Windows).
Aqui estão alguns links diretos para recursos relacionados sobre a segurança do thread VCL, caso você não queira pesquisar ...
Este aplicativo demonstra o uso de threads em Delphi. Como existem muitas coisas diferentes a saber, elas são divididas em seções diferentes para diferentes propósitos. Cada tópico possui pelo menos 1 unidade de formulário (incorporada em uma folha de guia) e pelo menos 1 unidade independente contendo sua funcionalidade além da interface do usuário. Isso é feito de propósito, para mostrar que os threads devem ser isolados de qualquer interface do usuário.
A própria forma principal não tem lógica nela. Tudo o que faz é incorporar os formulários nas guias. No manipulador de eventos FormCreate() , ele faz inúmeras chamadas para EmbedForm() que instancia um formulário para cada folha de guias.
Na verdade, usar o aplicativo é muito simples. Você apenas navega para uma das guias e cada uma terá suas próprias instruções.


Mostra como um arquivo pode ser baixado da Internet em um tópico. Existe uma única função universal definida DownloadFile() que executa o download. A interface do usuário tem 3 botões:
Por padrão, o URL a ser baixado é um arquivo de teste fornecido pelo ThinkBroadband.com, mas você pode usar qualquer URL que desejar. Você também pode escolher o local para salvar o arquivo. Esta é uma demonstração muito simples; portanto, o nome do arquivo/extensão do nome do arquivo local precisa ser ajustado às suas necessidades - ele não mudará automaticamente para o URL que você está baixando (como os navegadores normalmente fazem).

Mostra como atualizar uma barra de progresso a partir de um thread que está executando uma tarefa longa.

Demonstra o uso de uma conexão de banco de dados em um encadeamento e sincronizando dados com o thread da interface do usuário.

Demonstra vários threads que consomem ciclos maciços de CPU para carregar o teste do seu processador.