Fala, galera!
Acredito que muitos de vocês já tenham usado, estudado ou ao menos ouvido falar de Multithreading do Delphi. As bibliotecas deste recurso estão presentes desde a versão XE7, porém, por serem relativamente recentes, às vezes esquecemos de sua existência. O objetivo deste artigo é apresentar um cenário no qual o uso de Multithreading pode trazer uma grande vantagem em relação ao tempo de resposta de uma aplicação.
Introdução
Em março deste ano, apliquei um treinamento sobre Delphi Seattle na empresa em que eu trabalho para apresentar os recursos das versões mais recentes do Delphi, entre eles, claro, a biblioteca de Multithreading. Com esse mecanismo, podemos distribuir processamentos complexos em threads distintas, executando-as ao mesmo tempo. Por estarem em fluxos paralelos, o tempo de término destes processamentos é evidentemente menor do que uma abordagem sequencial (ou “linear”).
Muitos me questionaram sobre a aplicação de um processamento paralelo em um cenário mais próximo da realidade, portanto, dediquei os últimos dias para elaborar este artigo.
Cenário
Considere a emissão de um relatório de pedido que possui as seguintes seções:
- Dados do cliente (nome, endereço, CPF…);
- Dados do pedido (data, total, forma de pagamento…);
- Dados dos itens do pedido (produto, descrição, quantidade…).
Para que a emissão seja realizada, é necessário consultar os dados dessas três seções de forma separada. Como exemplo, considere que cada consulta demore aproximadamente os tempos abaixo:
- Dados do cliente: 2 segundos
- Dados do pedido: 3 segundos
- Dados dos itens do pedido: 4 segundos
Habitualmente, a codificação para a emissão deste relatório poderia ser:
1 2 3 |
ConsultarDadosCliente; ConsultarDadosPedido; ConsultarDadosItensPedido; |
Para simular o tempo dispendido com essa operação, programei um Sleep() dentro de cada método com a respectiva duração:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
procedure ConsultarDadosCliente; begin Sleep(2000); end; procedure ConsultarDadosPedido; begin Sleep(3000); end; procedure ConsultarDadosItensPedido; begin Sleep(4000); end; |
Em seguida, adicionei também duas variáveis para calcular o tempo gasto e uma mensagem para exibi-lo ao término das consultas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
procedure EmitirRelatorio; var Inicio: TDateTime; Fim: TDateTime; begin Inicio := Now; ConsultarDadosCliente; ConsultarDadosItensPedido; ConsultarDadosPedido; Fim := Now; ShowMessage(Format('Consultas realizadas em %s segundos.', [FormatDateTime('ss', Fim - Inicio)])); end; |
Ao executar o método de emissão, receberemos a mensagem:
Melhorando a funcionalidade com processamento paralelo
O propósito deste artigo é mostrar que podemos melhorar isso. Com o recurso de processamento paralelo, podemos distribuir cada consulta em uma thread e executá-las simultaneamente. Para isso, trabalharemos com a classe TTask
, da unit System.Threading
, utilizando a seguinte sintaxe:
1 2 3 4 5 6 7 8 9 10 11 |
uses System.Threading; { ... } var Task: ITask; begin Task := TTask.Create({método}); Task.Start; end; |
Basta então criar uma Task para cada consulta e disparar todas elas?
Sim, mas há uma condição que exige a nossa atenção. Você deve ter notado que cada consulta tem uma duração diferente. Isso significa que, se executarmos todas elas em threads, é possível que as consultas mais demoradas não sejam finalizadas a tempo. Caso isso ocorra, os dados dessas seções não serão exibidos no relatório.
Para evitar esse comportamento, criaremos um array de ITask
e, ao final, utilizaremos um método chamado WaitForAll
para solicitar que o processamento principal só prossiga quando todas as threads forem concluídas. Confira a codificação:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
procedure EmitirRelatorio; var Tasks: array [0..2] of ITask; Inicio: TDateTime; Fim: TDateTime; begin Inicio := Now; Tasks[0] := TTask.Create(ConsultarDadosCliente); Tasks[0].Start; Tasks[1] := TTask.Create(ConsultarDadosPedido); Tasks[1].Start; Tasks[2] := TTask.Create(ConsultarDadosItensPedido); Tasks[2].Start; TTask.WaitForAll(Tasks); Fim := Now; ShowMessage(Format('Consultas realizadas em %s segundos.', [FormatDateTime('ss', Fim - Inicio)])); end; |
Agora, ao executar o mesmo método de emissão, este é o resultado de tempo total:
Concluindo, com o processamento em paralelo, reduzimos a emissão do relatório para metade do tempo!
Agora, o TParallel.For
Um fechamento de caixa é outro cenário bem comum no qual o Multithreading pode ser aplicado, já que consiste em uma série de operações, como conferência de valores, cálculo de entradas e saídas, geração de saldos e até a produção de gráficos.
Mesmo assim, para dar continuidade sobre este recurso, vou apresentar mais um cenário. Imagine uma rotina de processamento de arquivos em lote através da seguinte codificação:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var ListaArquivos: TStringList; i: integer; begin ListaArquivos := TStringList.Create; ListaArquivos.Text := CarregarArquivos; for i := 0 to Pred(ListaArquivos.Count) do begin ProcessarArquivo(ListaArquivos[i]); end; ListaArquivos.Free; end; |
Observamos, claro, que um arquivo será processado por vez, já que o loop é iterativo e sequencial. Esse mesmo procedimento também pode ser distribuído em threads paralelas, no entanto, para este caso, não utilizaremos o TTask
. Devemos utilizar um comando chamado TParallel.For
, contida na mesma biblioteca. Além disso, como haverá um acesso concorrente ao objeto ListaArquivos
, é necessário utilizar um mecanismo de semáforo, representado pelo método TThread.Queue
no código abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var ListaArquivos: TStringList; begin ListaArquivos := TStringList.Create; ListaArquivos.Text := CarregarArquivos; TParallel.For(0, Pred(ListaArquivos.Count), procedure (i: integer) begin TThread.Queue(TThread.CurrentThread, procedure begin ProcessarArquivo(ListaArquivos[i]); end) end); end; |
Explicando: ao invés de usar um único fluxo de processamento, o TParallel.For
distribuirá as iterações em threads, ou seja, o loop será “dividido” em fluxos paralelos, reduzindo (bastante!) o tempo.
Fico por aqui, pessoal! Espero que esse artigo incentive a utilização dessa poderosa biblioteca do Delphi.
Um grande abraço!
Caramba André que top a maneira que você explicou.
Você não tem noção como isso me abriu a mente. Vou refatorar uma parte do meu sistema kkkk
Valewww
Hahaha, nossa, que bom!
Espero que abra a mente de muita gente também!
Obrigado, Oliveira!
Uso todos os recursos de processamento paralelo desde a primeira versão.
Excelente, Jordy!
Isso significa que você já vem tirando proveito do processamento paralelo há muito tempo. 🙂
Muito bem explicado. Parabéns!
Obrigado, André! Grande abraço!
Conteúdo muito bom e bem organizado, gostei… Só não ficou muito claro pra mim, o código:
No caso, qual é a finalidade do TThread.Queue nessa situação?
Abraços, professor!
Olá, Danilo!
Essa instrução é necessária para que o mesmo objeto (neste caso, “ListaArquivos”) não seja acessada ao mesmo tempo por duas ou mais threads. Elas devem aguardar a thread atual terminar o processamento com o objeto para poder usá-lo. É o conceito de semáforo que normalmente é abordado na disciplina de Sistemas Operacionais nos cursos superiores.
Abraço!
Perfeito amigão, obrigado!
Bom dia André,
Cara que top isso hein, show.
Já estou pensando em criar uma função para chamar esse método em outros processo do sistema, passando como parâmetro o método a ser executado.
Abraço André.
Boa, Daniel! É uma ótima estratégia para usar o paralelismo!
Se der certo, me avise para que eu possa complementar esse artigo.
Abraço!
Muito bom !
Obrigado, Geison!
Andre ,
Como sempre seus artigos são otimos . Grato por compartilhar seu conhecimento conosco .
Muito obrigado, Oteniel!
Espero continuar contribuindo sempre para a comunidade Delphi!
Abraço!
Muito bom seu artigo!!! Mais uma técnica para aplicar!!!!
Grande abraço, e obrigado por compartilhar…
Obrigado, Cleiton!
Isso aí, mais um recurso à nossa disposição! 🙂
Bom dia André. Estou desenvolvendo um algoritmo genético em Delphi, usando generics, anonimous methods e multithreading. Seus artigos tem sido de grande ajuda! Como você faria para PAUSAR uma iTask, e depois retomar o processo de onde parou?
Olá, Ricardo, tudo bem? Desculpe-me pela demora.
Excelente pergunta. Ainda não tive a oportunidade de trabalhar em um cenário no qual é necessário pausar uma ITask, mas vou realizar alguns testes e entrar em contato com você por e-mail, ok?
Abração!
Bom dia André, ótimo o artigo. Me tornei leitor assíduo aqui. Eu quero usar o recurso para geração de um arquivo que vem de um ecf. Em modo de debug tá perfeito, porém quando eu passo para release, no momento de executar o procedimento na thread, o o Windows congela. Só posso recorrer ao CTRL ALT DEL ou aguardar.
Você sabe por que funciona em debug e em release não? Desde já, muito obrigado.
Olá, Ubirajara!
Vou entrar em contato com você para solicitar mais detalhes.
Um abraço.
Muito bom parabéns, não sei se estou falando besteira mais nesse caso daria para usar um progressbar usando o TTask sem travar o formulário ?
Olá, Erasmo!
Exatamente! É possível utilizar TTask para atualizar um controle visual, como um componente TProgressBar:
Legal, hein?
Abraço!
Prezado André.
Eu estou precisando utilizar o recurso de processamento paralelo, porém a versão que estou trabalhando é o Delphi 2007 e como você comentou que as bibliotecas deste recurso estão presentes desde a versão XE7, entendo que não conseguirei utilizar este recurso, correto? Tem alguma alternativa que você sugeriria aplicar este recurso na versão do Delphi 2007?
Att.,
Júlio Bitencourt.
Olá, Julio, boa noite!
No Delphi 2007, você pode utilizar a biblioteca OmniThreadLibrary, disponível no link abaixo:
http://otl.17slon.com/
Não tive a oportunidade de testá-la, mas, pelos comentários que já acompanhei na internet, parece ser bem produtiva.
Abraço!
Parabéns pelo artigo, só queria tirar uma dúvida, caso tenha rodando um processo pelo TTask e o cliente clique para fechar o sistema, existe algum teste para saber se tem algum Task rodando para avisar e evitar que a aplicação seja finalizada?
Olá, Vinícius!
Sim, é possível utilizar o enumerado
TTaskStatus
para verificar o status de uma TTask.Veja mais na documentação oficial da Embarcadero:
System.Threading.TTaskStatus
Há também um artigo do MVP Robert Love exemplificando o uso desse recurso:
PPL – TTask Exception Management
Obrigado! Abraço!
Olá André! Muito esclarecedor o seu artigo sobre TTask.
Cheguei aqui porque está justamente pesquisando sobre ele, pois tenho um módulo no meu programa que preciso saber se um FTP está ativo ou não. Eu faço isso quando o programa é aberto e, caso o FTP esteja off-line, o programa leva cerca de 5 segundos para “descobrir” isso. Pensei em colocar como uma tarefa, ou seja, deixo o usuário começar a trabalhar enquanto faço a verificação.
Eu já tenho uma procedure na minha unit que faz isso. Bastaria eu usar:
Seria só isso mesmo? Eu colocaria isso no on-create do meu form principal?
Agradeço antecipado a ajuda e parabenizo mais uma vez pelo blog!
Um abraço!
Olá, Adilson, boa noite!
Em primeiro lugar, parabenizo você pela ótima escrita! Ficou muito fácil entender a sua dúvida.
Bom, Adilson, a sua necessidade é justamente o que o ITask se propõe: executar uma tarefa em paralelo para não prejudicar a experiência do usuário. Acredito que o seu código irá funcionar conforme esperado.
Opcionalmente, você pode escrever tudo em uma linha só:
Grande abraço e muito obrigado!
Boa tarde André, tenho m agendamento de relatórios que executa conforme a data e hora que o usuário quer.
Nesse agendamento eu crio caso vários componentes de conexão tem tempo de execução. Exemplo provider para relatórios:
Como estou trabalhando com Thread, obtenho o erro que o component name already exists, caso agende dois relatórios para serem executados ao mesmo tempo.
O Task irá resolver esse meu problema?
Olá, William, boa noite!
Sim, o TTask é uma boa opção para realizar esse processamento paralelo, porém, mesmo com thread esse erro não deveria ocorrer.
Se o TDataSetProvider é criado dentro da thread, não deveria existir este conflito de nomes, já que são fluxos de processamento separados. Talvez o erro esteja relacionado com algum recurso compartilhado entre as threads, ou seja, estão utilizando um mesmo objeto ao mesmo tempo.
Experimente depurar a aplicação e descobrir a linha em que o erro ocorre. Dessa forma será mais fácil identificar a origem do conflito.
Abraço!
Tenho um agendamento de relatórios. O usuário cadastra os relatórios que ele quer agendar e recebe no email, ftp etc…
Resolvi colocar essa rotina em thread, Está funcionando até que bem, mas quando mais de um agendamento é executado no mesmo horário e como crio os componentes de conexão em runtime, está acontecendo de criar componentes com o mesmo nome. Veja a mensagem de erro: A component named dspRelat already exists. Nesse caso o sistema de agendamento tentou criar mais de um provider com o mesmo nome.
O task resolveria esse problema?
Olá, William, boa noite!
Sim, o TTask é uma boa opção para realizar esse processamento paralelo, porém, mesmo com thread esse erro não deveria ocorrer.
Como o TDataSetProvider é criado dentro da thread, não deveria existir este conflito de nomes, já que são fluxos de processamento separados.
Você define o nome do componente (Name) em tempo de execução? Se este for o caso, experimente definir um nome aleatório para o componente. Para isso, você pode usar o
FormatDateTime
. No exemplo abaixo, o nome do componente recebe os minutos, segundos e milisegundos atuais:Abraço!
Olá André, excelente artigo…
Fiquei com uma dúvida referente ao exemplo da lista de arquivos com “TParallel.For”
Se o objeto “ListaArquivos” tiver 100 arquivos, serão geradas 100 threads? Teria como limitar um número máximo de threads?
Olá, Cristiano, boa noite!
Ótima pergunta! Sim, existe uma forma de limitar a quantidade de threads por meio de um parâmetro em uma das sobrecargas do TParallel.For.
No vídeo abaixo, o MVP Kelver Merlotti explica a utilização deste parâmetro (já deixei no tempo exato em que ele inicia a explicação):
https://youtu.be/YnFcxXYPskg?t=14m8s
Abraço!
Olá, André…
Estou com uma situação que realmente esta dificultando o entendimento.
Estou apenas simulando para entender melhor o comportamento de uma task.
O código abaixo se executado interação por interação funciona, porém, se executado diretamente não. Isto me leva a crer que ao usar uma procedure dentro da task ela não é ThreadSafe.
Veja o código e me diz se consegue dar uma luz.
Olá, Rodrigo.
Sua dúvida é interessante. Vou entrar em contato com você.
Abraço!
Olá André, tira uma dúvida por favor.
É preciso liberar da memória a task no final do processo ou o compilador cuida disso?
Olá, Roberto! Ótima pergunta!
Como declaramos a task como um tipo de Interface (
ITask
), o compilador usa o esquema de contagem de referências, liberando o objeto da memória quando atingir zero. Portanto, não é necessário liberar a task.Abraço!
Bom dia, André. Tenho uma aplicação(serviço) que deverá ler uma tabela de tempos em tempos (infelizmente não consegui fazer com o FDEventAlert, então usei um Timer). Mas o fato é que as vezes os registros acumulam, por isso resolvi utilizar sua solução com TParallel, mas não sei se foi a melhor escolha, pois quando está em execução, a tela fica travada e não mostra a quantidade de registros em processamento. Segue o código e desde já, agradeço:
Note que eu tento parar o Timer, enquanto o sistema processa a rotina IntegracaoLaboratorio e ao final da rotina, executo-a novamente. Enfim, o que pode ser melhorado nessa rotina?
Olá, Chagas!
Vou entrar em contato com você para entender melhor o cenário.
Abraço!
Ótimo artigo
Obrigado!
Bom Dia. Esse artigo é simplesmente fantástico. Abre muitas possibilidades. Tenho uma rotina que copia vários arquivos de determinada pasta para determinada pasta usando um for. Demora em torno de 02:15 minutos, alterando para o for paralelo demorou 10 SEGUNDOS!!!
Apenas tenho uma dúvida. como faço para ficar no TParallel.For até terminar todo o processo? Ele está saltando como se fosse uma thread normal. Existe algum tipo de Wait For neste caso?
Mais uma vez: Parabéns! Irei começar a ler todos os seus artigos.
Opa, Leonardo, que bom que o artigo foi útil pra você! 🙂
Excelente pergunta! Não tenho a resposta neste momento, mas acredito que a espera pelo término da execução de todo o processamento paralelo é uma característica somente do TTask. De qualquer forma, vou pesquisar a documentação e, caso eu encontre algo, envio no seu e-mail!
Abração!
André blz,
Parabéns pelo blog.
Saberia dizer se tem como fazer thread no Delphi 5 ou algum comando similar que funcionaria no Delphi 5 para controlar processos demorados?
Vlw.
Olá, Carlos!
Sim, no Delphi 5 existe a classe
TThread
. Basicamente, Basta criar uma nova classe herdando dela e sobrescrever o método Execute.Clique aqui para acessar um artigo bem explicativo do Luís Gustavo Fabbro.
Abração!
Muito bom seu artigo Andre, parabéns.
Uma dúvida: teria como manipular objetos de interface utilizando TTask?
Tentei chamar TThread.syncronize dentro de u m TTask porém informa que não é possível chamar TThread neste local.
Olá, Jean! Excelente pergunta!
Sim, é possível manipular objetos visuais com
TTask
. Para isso, você deve utilizar TThread.Queue para “enfileirar” o acesso ao objeto, evitando erros de concorrência. Veja o exemplo abaixo, no qual usa umaTTask
para preencher uma barra de progresso comTParallel.For
:Abraço!
Boa tarde.
André parabéns pelo artigo.
Tenho duas procedures, uma eu procuro informações, gravo em um ClientDataSet e mostro no dbGrid, em outra eu percorro o ClientDataSet e envio essas informações para uma API. Dessa forma a 2ª procedure aguarda a 1ª ser finalizada. Como eu poderia usar TTask e fazer as duas ao mesmo tempo?
Olá, Welder, tudo bem? Peço desculpas pela demora. Eu estava em uma viagem a trabalho.
Para respondê-la com mais detalhes, precisamos, primeiramente, compreender a concorrência de recursos. Neste conceito, dois processos não podem acessar o mesmo recurso ao mesmo tempo para não gerar conflitos de acesso. No seu caso, este recurso é o ClientDataSet. Enquanto a primeira procedure está gravando informações, uma outra procedure em paralelo não pode acessar o ClientDataSet. Se as duas procedures em paralelo tentarem acessar o mesmo objeto, ocorrerá um Access Violation ou causará um travamento na aplicação.
Neste cenário, uma solução viável é trabalhar com enfileiramento de processos, usando o TThread.Queue, no qual utilizo no último bloco de código do artigo.
Em resumo: você pode utilizar TTask para paralelizar essas rotinas (gravação e leitura), desde que estejam dentro da chamada do TThread.Queue.
Abraço!
Boa tarde André,
Estou fazendo assim para chamar a procedure de busca do produto:
Também testei com Array como fez no exemplo,
Não aparece no grid do form, digo visual ai fica lendo e trava sem resposta.
O erro é: list index out of bounds (1), acredito que é erro ao visualizar no grid.
Depois desse erro, clicando na tela, abaixo vai aparecendo como se tivesse pesquisado, mas não aparece.
Olá, Fabrício.
Se você estiver utilizando apenas uma procedure, não é necessário utilizar um array de TTask.
Além disso, caso a intenção seja executar a procedure de forma assíncrona, considere a utilização do método TThread.CreateAnonymousThread.
Abraço!
Muito bom seu artigo…
Mas no caso de eu precisar sincronizar com a Thread principal.
Como seria?
Olá, Eduardo, tudo bem?
Não sei se entendi muito bem a sua questão, mas, para que uma thread em paralelo sincronize objetos na thread principal (como o preenchimento de um componente visual), deve-se utilizar o comando
TThread.Queue
, que foge um pouco do propósito deste artigo.Pretendo elaborar um artigo detalhado sobre
TThread
futuramente.Abraço!
Boa tarde Amigo Celestino,
Gostaria que analisa-se esse código da task abaixo, está correto?
Olá, Fabrício, tudo bem?
O código está correto, porém, se a intenção é executar as 6 instruções SELECT em paralelo, eu sugiro criar um array de ITask e atribuir a execução de cada instrução SELECT em uma Task diferente, semelhante ao exemplo que está no artigo.
Abraço!
Boa noite, excelente trabalho, mas estou com uma dúvida. Eu preciso fazer uma aplicação que recupera uma lista de telefones e utiliza um WebService para enviar SMS. Eu estou tentando paralelizar esse trabalho para ganhar tempo. Então fiz o código da seguinte forma:
// aqui antes eu faço o cálculo iQtdeProc quantos loops vou fazer no TParallel.For
Aqui no final não sei tratar quando a fila de threads terminou de executar para eu habilitar os botões.
Também não sei se foi a melhor forma, mas foi a única que funcionou. E só depois que fiz da forma que está aqui no seu site que consegui fazer funcionar sem erro.
Agradeço desde já.
Olá, Eduardo, como vai?
Não vi nada de errado no seu código. Pelo contrário, essa é a forma mais ideal de utilizar o
TParallel.For
.Eduardo, o
TParallel.TLoopResult
possui uma propriedade chamada Completed. Se não me engano, essa propriedade indica se todas as threads já foram concluídas. Vale a pena fazer um teste!Abraço!
Obrigado André, pela resposta tão rápida. Eu utilizo o TParallel.TLoopResult, conforme o código abaixo:
Esse código está logo após o código que coloquei no post anterior, isso dentro do clique o botão. Mas o que está acontecendo é que os controles são habilitados e a mensagem é exibida. Mas ainda ficam processos atualizando os contadores, eu gostaria de habilitar os controles e dar a mensagem só depois que o processo realmente terminasse. Será que você poderia me ajudar?
Olá, Eduardo.
Que estranho. A propriedade
Completed
deveria retornarTrue
somente quando todas as iterações já estivessem concluídas. Inclusive, a utilização dessa propriedade é apresentada nos exemplos do livro More Coding In Delphi de Nick Hodges, que estão nesse repositório público do BitBucket. Nos exemplos, ele utiliza a propriedade da mesma forma como você utilizou, e o resultado fica correto.Eduardo, pensei que, talvez, as iterações estão sendo encerradas, mas há um “atraso” na atualização visual (contadores). Este comportamento pode ocorrer quando as iterações do
TParallel.For
compartilham dos mesmos objetos (ponteiros).Se a atualização visual realmente está sincronizada com as iterações, infelizmente desconheço o motivo da propriedade
Completed
receberTrue
antes da finalização das threads.Obrigado mais uma vez pela resposta, mas agora fiquei com uma dúvida, dentro do TThread.Queue eu coloco o código que vai atualizar a minha tela? Porque no meu caso eu coloquei o código que faz toda a tarefa desde a recuperação dos números de telefones até o envio do SMS pelo o WebService, e só dessa forma que não deu erro. Só que acontece esse problema de não parar no final do TParallel.For. Será que estou fazendo errado?
Olá, Eduardo!
O código aparentemente está correto. A atualização da GUI realmente deve estar dentro de
TThread.Queue
em função da utilização de componentes visuais.Eu tenho uma nova suspeita: o
TParallel.For
é finalizado, porém, como a atualização da GUI está dentro de umaTThread.Queue
, ela entrará em uma “fila de execução” (por isso o nome “Queue”), portanto, essa rotina será executada em um futuro próximo e, ao que parece, esse “futuro” está sendo após a finalização das iterações doTParallel.For
. Em outras palavras, oTParallel.For
e oTThread.Queue
ficam “fora de sincronia”.Uma tentativa é utilizar o comando
TTask.WaitForAll
antes de entrar na condição deTLoopResult.Completed
. Essa instrução aguarda a conclusão de todas as tasks em execução. Não fiz o teste, mas há uma possibilidade de dar certo.Abraço!
André.
No meu sistema tenho uma connection que é utilizada por todo o sistema.Se for utilizado paralelismo não terei problemas quando duas ou mais consultas sendo executadas em paralelo solicitarem conexão para o BD?
Olá, Jocimar, boa noite!
Primeiramente, peço desculpas pela demora. Tive alguns problemas de saúde essa semana. 🙁
Jocimar, se a sua conexão com o banco de dados é unica e compartilhada, ou seja, utilizada por qualquer local do sistema, significa que ela não é compatível com paralelismo. Em outras palavras, essa conexão não é thread-safe. Muitos programadores cometem o equívoco de definir a conexão com o banco de dados como um Singleton (que não é thread-safe) para ganhar performance com threads. Porém, como a conexão é compartilhada, cada thread terá que esperar a thread atual terminar o uso da conexão para então utilizá-la.
Portanto, para aumentar a performance nesse cenário, a conexão com o banco de dados terá que ser criada dinamicamente por cada thread. Vale destacar que, dessa forma, 3 threads significam 3 conexões com o banco de dados ao mesmo tempo. É importante avaliar se isso não será uma restrição.
Abraço!
Olá André,
Eu estou tentando implementar as tasks no meu sistema, que já está 99% feito, é um sistema comercial robusto e quero reduzir o tempo que demora em certas telas como a de configurações. Nesta por exemplo, eu tenho vários métodos que percorrem o objeto dmPrincipal (DataModule com classes), que é carregado ao abrir o sistema, e com os dados dele eu preencho o form.
Eu não estou conseguindo usar as tasks porque elas sempre me retornam erros, no caso, o seguinte erro: https://prnt.sc/omh534
Exemplo de uso:
Quando ele passa pelo “TTask.WaitForAll(Tasks)” ele da o erro da print.
Dês de já, Obrigado!
Olá, JRM, tudo bem?
Bom, eu teria que analisar melhor a origem do problema, mas, a princípio, eu suspeito que o erro esteja acontecendo em função da concorrência de recursos. Este conceito declara que dois ou mais processos não podem acessar o mesmo recurso ao mesmo tempo para não causar conflitos. No seu caso, este recurso é o “dmPrincipal”. As duas Tasks estão usando este objeto de forma paralela. Quando as duas Tasks tentam acessá-lo, portanto, pode ocorrer um Access Violation ou um travamento na aplicação.
Neste cenário, há duas soluções:
1) Trabalhar com enfileiramento de processos usando o
TThread.Queue
, no qual utilizo no último bloco de código do artigo;2) Criar uma instância da classe “TdmPrincipal” dentro de cada método, de modo que cada Task trabalhe com instâncias diferentes.
Abraço!
Qual a diferença de Task[0].Start para Task[0].ExecuteWork ?
Olá, Lindemberg!
O método
ExecuteWork
trabalha com replicação de Tasks, definida na criação da Task por meio do parâmetro CreateFlags. Este método é utilizado apenas em situações específicas.O uso mais comum de uma Task é com o método
Start
.Abraço!
Bom dia, professor.
Estava com um problema de lentidão em meu código e ao ler o seu artigo vi que daria pra melhorar, porém o TParallel.For não está sendo executado e não compreendi o porquê. Se puder me ajudar…
Eu tinha um laço cujo está comentado, pensei em trocar pelo TParallel.For:
O meu problema é que ao chegar na linha do For, ele já cai para o último end, como se o loop já tivesse chegado ao fim. O que poderia ser?
Desde já agradeço pelo seu conhecimento compartilhado.
Olá, Eduardo, boa tarde!
O
TParallel,For
, com este código, realmente deveria funcionar. Porém, estou desconfiando que o problema esteja nos parâmetros.Experimente remover o “1” e deixar apenas dessa forma:
Abraço!
Olá André,
Eu tenho um sistema em fila, porém em uma fila no sql server.
Um cliente específico tem um fluxo absurdo, e como eu demoro 3 segundos para percorrer um registro, pois é um processo de geração de relatórios em PDF, meu sistema está gargalando.
Eu encontrei sua solução, justamente buscando por Threads em Delphi, vc acredita ser possível utilizar Thread para Ler, inserir e atualizar meu banco de dados e ainda gerar meus pdfs?
Obrigado,
Leandro
Olá, Leandro, boa noite!
Bom, como não conheço a estrutura do projeto, não posso afirmar com toda a certeza, mas acredito que seja possível, sim. Tecnicamente, você teria que mover a leitura, inserção, atualização no banco de dados e geração dos PDFs para uma classe isolada e instanciar essa classe em diferentes threads. Talvez, você pode até mesmo usar um array de Tasks e executá-lo paralelamente. Com isso, seria possível percorrer 3 ou 4 registros ao mesmo tempo.
Mas atenção, Leandro: para que este recurso funcione como esperado, não pode existir concorrência de objetos. Por exemplo, as threads (ou tasks) não podem compartilhar o mesmo objeto de conexão com o banco de dados. Isso causaria um conflito de acesso e poderia até travar a aplicação. Digo isso porque este problema acontece com muitos desenvolvedores.
Ao trabalhar com processamento paralelo, os objetos devem ser independentes.
Caso ainda tenha dúvidas, envie um e-mail para [email protected]!
Abraço!
Tudo bem?
Obrigado por mais um artigo excelente.
Teria como fazer esse procedimento com Consultas SQL?
Tenho um cenário da seguinte forma:
Consulta 1 – 2 segundos
Consulta 2 – 3 segundos
Consulta …
Consulta 100 – 2 segundos
Tudo dá em torno de 230 segundos.
Preciso urgentemente dividir isso para reduzir o tempo, pois não tenho consulta com mais de 10 segundos e o tempo total de todas elas tá dando mais de 200 segundos.
Como são 100 consultas diferentes, queria a melhor forma para conseguir esse objetivo?
Já tentei criando vários componentes de query, mas creio que como é a mesma conexão o tempo ficou o mesmo.
UPDATE:
Eu dei uma lida melhor e consegui.
Estava tentando reaproveitar minha Thread mas com a sua ficou uma bala.
Apenas tive que utilizar Frames e em cada frame criei um Connection e uma Query.
Meu amigo… 100 consultas em segundos!!!!
Olá, Lucas, tudo bem? Peço desculpas pela demora.
Que bom que deu certo! Esse recurso de processamento paralelo é mágico, né? rsrs
Nós também conseguimos melhorar uma série de funcionalidades no nosso sistema ao paralelizar requisições no banco de dados.
Abração, e obrigado pelo retorno!
Estou usando em um comando para abrir gaveta que dava lag na hora de passar troco no PDV, agora o troco aparece instantâneo, obg
Boa, Valter! É isso aí!
Olá André. Muito bom o conteúdo… Melhor ainda a atenção que dá para os comentários. Li todos e alguns foram de grande importância para um melhor entendimento sobre compartilhamento de recursos na utilização de TASK com banco de dados.
Mas não encontrei nenhuma dúvida, com relação a tratamento de exceções, usando TASK. Não estou conseguindo tratar uma exceção e passar para a thread principal. Tem alguma dica?
Olá, Marcelo, bom dia.
Excelente pergunta!
O tratamento de exceções depende de como você está usando a Task. Se for uma array de Tasks, basta envolver o comando
WaitForAll
em um bloco try/except:Porém, se estiver usando apenas uma Task, você pode tirar proveito da função
AcquireExceptionObject
para obter a exceção e encaminhá-la para a main thread. Veja um exemplo:Espero que lhe ajude!
Abração!
Olá André, Parabéns pelo conteúdo, tenho uma tela de vendas no meu sistema que preciso ficar consultando o banco de dados a cada 3 segundos e listando a consulta em uma grid. Como eu poderia aplicar isso em uma TASK para melhorar a experiencia do usuário? Hoje a tela da algumas pequenas travadas.
Olá, Rafael, bom dia!
Você pode usar uma Task exclusivamente para recuperar os dados (consultar no banco de dados) e enviá-los para a thread principal para ser exibida ao usuário. Dessa forma, a tela não terá mais a responsabilidade de realizar essa consulta, evitando travamentos visuais.
Mesmo assim, devo dizer que fiquei cismado com esse tempo de 3 segundos. Se possível, reveja essa necessidade!
Abraço!
Ola, otimo artigo. Tenho uma duvida, tenho que importar para um SQLite 6 tabelas de um servidor Rest, eu criava um TTask, e fazia todos um seguida do outro nesta, bem vendo seu artigo, tentei transformar em um paralelismo com TParallel.For, usando o TThread.Queue, funcionou muito rápido, porem os componentes visuais TAniIndicator, não se apresentaram, ou seja ficou na fila e como no fim faça ele Visible:=false, ele nem foi mostrado, lendo suas resposta as duvidas anteriores, acabei tentando mudar para mas não consegui que Thread principal ficasse liberada para ser executada, já que Queue travou ela, alguma solução.
Olá, Carlos, bom dia!
Não tenho certeza se vai resolver no seu caso, mas experimente trocar
TThread.Queue
porTThread.Synchronize
.Nesse tópico do StackOverflow tem uma explicação sobre a diferença dos dois:
https://stackoverflow.com/questions/42280937/delphi-queue-and-synchronize
Abraço!
Boa tarde, não consegui implementar esse método em um relatório… Tenho 3 procedures, cada possui uma consulta. Segue abaixo o código. (Obs: está me apresentando erro, one or more errors occurred).
Olá, Jefferson, tudo bem?
Você comentou sobre 3 procedures, mas no código existem 4. Será que a declaração da sua variável “Tasks” está incorreta? Ela deve ser:
Além disso, vale fazer duas ressalvas:
1) Verifique se
System.Threading
está na seção “uses” da sua classe;2) Verifique também se as procedures acessam algum objeto compartilhado (conexão com banco de dados, componente visual, DataSet, etc.). O acesso concorrente a um mesmo objeto pode gerar erros na execução da Task.
Abraço!
São 4 procedures mesmo, foi erro de digitação no comentário, mas prosseguindo, dentro de cada procedure tenho uma consulta SQL via query. Pode ser exatamente isso então.
Obs.: Uso firedac para conexão, e banco Firebird.
Jefferson, experimente criar uma Query dentro de cada procedure. Acho que vai resolver!
Olá, André.
Tem como eu passar um parâmetro para uma thread anonima? Exemplo : Threadimpressao.Start(cod1,cod2,cod3);
Eu quero disparar a thread de impressão mas nao posso pesquisar no banco dentro da thread na hora da impressão porque os valores já podem ter mudado… ai quero passar o valor na hora que chamo a thread…
Olá, Luiz, tudo bem?
Uma Thread anônima recebe apenas um método anônimo como parâmetro. Porém, este método anônimo pode chamar outro método que tenha parâmetros. Veja um exemplo adaptado ao seu caso:
Nesse post do Stack Overflow também tem essa mesma resposta: https://stackoverflow.com/a/34890504
Bom dia André, tudo bem?
Estou começando no mundo da programação e principalmente com o Delphi.
Estou utilizando uma rotina que faz a transferência de um arquivo Zip via ftp para meu servidor, e isso está funcionando perfeitamente.
Porém como o arquivo é um pouco grande e a velocidade de internet nem sempre ajuda, decidi sobrepor a tela com um Pannel e uma Circular BarProgress que irá funcionar durante a transferência do arquivo.
A procedure que é responsável pela parte visual e atualização dessa barra circular funciona, já testei separadamente, mas quando eu utilizo ela na thread como nos exemplos acima ela não é executada. Poderia me ajudar a identificar o que está errado?
Essa é a minha procedure para Barra cirular ser exibida e atualizada:
Na minha procedure de transferência eu executo a thread dessa forma:
É minha primeira postagem aqui, me desculpem se minha postagem não estiver dentro do padrão, dese já agradeço.
Olá, Thiago, como vai?
Primeiramente, peço desculpas pela demora.
A apresentação de uma tela de espera durante um processamento pode ser feito de duas formas:
1) O processamento é realizado na thread principal e a tela de espera é exibida em uma thread separada;
2) O processamento é realizado em uma thread separada e a tela de espera é exibida na thread principal.
Independentemente de qual solução você irá aplicar, eu sugiro que você utilize a classe
TThread
, e nãoTTask
. A classeTTask
normalmente é utilizada em processamentos assíncronos ou em um conjunto de tarefas para execução paralela. Já a classeTThread
, por sua vez, permite que você faça uma única execução em paralelo, que pode ser o processamento em si ou uma tela de espera.Mesmo assim, se você preferir usar a classe
TTask
, experimente mover o processamento para ela e exibir a tela de espera na thread principal, ao contrário do que você está fazendo.Caso não funcione ou se surgir novas dúvidas, envie um e-mail para [email protected], ok?
Abraço!
Boa tarde mestre!! Eu tentei usar o Task no Firemonkey, mas parece não funciona, sabe dizer se não funciona mesmo ou o problema está entre o teclado e a cadeira daqui?? rss.
Olá, Sergio! Desculpe pela demora.
Infelizmente não tenho muito conhecimento em Firemonkey mas, teoricamente, deveria funcionar como na VCL.
Eu inclusive tentei procurar algo na documentação oficial do Firemonkey, mas o servidor da Embarcadero está offline desde ontem. Mesmo assim, encontrei exemplos em outros fóruns de desenvolvedores utilizando o Task em projetos FMX, ao menos que exista alguma restrição ou configuração, mas não tenho conhecimento.
Abraço!
Opa! Boa Noite! Obrigado por responer!!! Eu usei um TCriticalSection com TThread Anonima, e deu certo aqui. Obrigado, depois vou testar o Task. Coloco aqui o resultado pra galera!!! Um Abraço e obrigado!!!!
Eu que agradeço pelo comentário, Sergio!
Queria parabenizar o André não só pelo belíssimo post, mas tbm pela paciência em responder todos desde 2017, internet precisa de mais pessoas assim e menos youtubers e outros que só criam conteúdo lixo afim de lucrarem com views!!!
Muito bom , Parabéns continue assim, desejo todo sucesso que uma pessoa possa ter!!!
Olá, Sergio.
Muito obrigado pelo seu feedback! Mensagens como a sua são o que me motivam a continuar esse trabalho!
Embora o blog esteja bem desatualizado quanto às publicações, pretendo retomá-las ainda este ano.
Abração!
OI André, boa noite! Parabéns pelo conteúdo! Fiz um teste: tenho uma rotina que precisa carregar em tela 8 consultas (algumas demoradas) diferentes, tentei utilizar o conceito de paralelismo para chamar cada procedure responsável por uma query, porém percebi que tem hora que uma ou outra Thread fica agarrada e não é encerrada.
Tenho o código no GITHUB se quiser.
Opa, Thiago, tudo bem?
Vou entrar em contato com você.
Abraço!
Boa noite, André, como faço para evitar o memory leak ao fechar o formulário clicando no X indevidamente utilizando a Thread abaixo, eu uso Delphi Xe2 e não tem a iTask como nesse artigo.
Desde já agradeço sua atenção.
Abraços.
procedure TfView.btn1Click(Sender: TObject);
var
MyThread: TThread;
begin
MyThread := TThread.CreateAnonymousThread(
procedure
begin
try
// Código que será executado na thread
Sleep(2000); // Exemplo de operação demorada (2 segundos)
TThread.Synchronize(TThread.CurrentThread,
procedure
begin
// Atualiza a interface do usuário
Log('Thread concluída');
end);
Sleep(2000);
finally
// Libera a referência à thread quando terminar
MyThread.Free;
end;
end
);
// Inicia a thread anonima
MyThread.Start;
end;
Olá, Ronaldo.
Não sei se entendi muito bem, mas acredito que a função “Log” esteja fazendo alguma referência ao formulário, certo?
Se esse for o caso, talvez a solução seria adicionar alguma condição para testar se o formulário existe (e está ativo) antes de executar a função.
Digo isso porque a Thread, em si, não deveria causar memory leaks, já que é um processo “isolado”.
Boa tarde! Desculpe o Log. É sim um Memo1.Lines.Add(const atexto: string) ou seja, atualiza um Memo no formulário.
Eu questionei isso porque estou tentando entender Thread para implantar em processos, mas ainda tenho receio caso o usuário feche a aplicação de forma indevida através de Control + Alt + Del, não quero deixar memory leaks na thread.
Abraços.
Entendido, Ronaldo.
Concordo com você. É necessário compreender bem o conceito de threads antes de aplicá-las em um projeto. Embora pareça simples, há algumas considerações que devem ser levadas em conta.