Exemplo de conjunto de encadeamentos Delphi usando AsyncCalls

Este é o meu próximo projeto de teste para ver qual biblioteca de encadeamentos para o Delphi seria a melhor para minha tarefa de "verificação de arquivos" que gostaria de processar em vários threads / em um pool de threads.

Para repetir meu objetivo: transforme minha "varredura de arquivo" seqüencial de 500-2000 + arquivos de uma abordagem não segmentada para uma abordagem segmentada. Eu não deveria ter 500 threads em execução ao mesmo tempo, portanto, gostaria de usar um pool de threads. Um conjunto de encadeamentos é uma classe do tipo fila que alimenta vários encadeamentos em execução com a próxima tarefa da fila.

A primeira tentativa (muito básica) foi feita simplesmente estendendo a classe TThread e implementando o método Execute (meu analisador de string encadeado).

Como o Delphi não possui uma classe de pool de threads implementada pronta para uso, em minha segunda tentativa, tentei usar o OmniThreadLibrary de Primoz Gabrijelcic.

O OTL é fantástico, possui um zilhão de maneiras de executar uma tarefa em segundo plano, um caminho a percorrer, se você quiser ter uma abordagem "disparar e esquecer" para entregar a execução encadeada de partes do seu código.

instagram viewer

AsyncCalls por Andreas Hausladen

Nota: o que se segue seria mais fácil de seguir se você primeiro baixar o código fonte.

Ao explorar mais maneiras de executar algumas de minhas funções de maneira encadeada, decidi também experimentar a unidade "AsyncCalls.pas", desenvolvida por Andreas Hausladen. Andy AsyncCalls - chamadas de função assíncronas unit é outra biblioteca que um desenvolvedor Delphi pode usar para aliviar a dor de implementar uma abordagem encadeada para executar algum código.

Do blog de Andy: Com o AsyncCalls, você pode executar várias funções ao mesmo tempo e sincronizá-las em todos os pontos da função ou método que as iniciou. A unidade AsyncCalls oferece uma variedade de protótipos de função para chamar funções assíncronas. Implementa um pool de threads! A instalação é super fácil: basta usar asyncalls de qualquer uma das suas unidades e você terá acesso instantâneo a coisas como "executar em um thread separado, sincronizar a interface do usuário principal, esperar até terminar".

Além do AsyncCalls (licença MPL) de uso livre, Andy também publica frequentemente suas próprias correções para o Delphi IDE como "Delphi Speed ​​Up"e"DDevExtensions"Tenho certeza que você já ouviu falar (se já não estiver usando).

AsyncCalls In Action

Em essência, todas as funções do AsyncCall retornam uma interface IAsyncCall que permite sincronizar as funções. IAsnycCall expõe os seguintes métodos:

 //v 2.98 de asynccalls.pas
IAsyncCall = interface
// espera até que a função termine e retorne o valor de retorno
função Sync: Inteiro;
// retorna True quando a função assíncrona é concluída
função Finalizada: Boolean;
// retorna o valor de retorno da função assíncrona, quando Finished for TRUE
função ReturnValue: Inteiro;
// informa ao AsyncCalls que a função atribuída não deve ser executada no threa atual
procedimento ForceDifferentThread;
fim;

Aqui está um exemplo de chamada para um método que espera dois parâmetros inteiros (retornando um IAsyncCall):

 TAsyncCalls. Invocar (AsyncMethod, i, Aleatório (500));
função TAsyncCallsForm. AsyncMethod (taskNr, sleepTime: inteiro): inteiro;
início
resultado: = sleepTime;
Dormir (sleepTime);
TAsyncCalls. VCLInvoke (
procedimento
início
Log (Formato ('concluído> nr:% d / tarefas:% d / dormiu:% d', [tasknr, asyncHelper. TaskCount, sleepTime]));
fim);
fim;

Os TAsyncCalls. O VCLInvoke é uma maneira de sincronizar com o thread principal (o thread principal do aplicativo - a interface do usuário do aplicativo). VCLInvoke retorna imediatamente. O método anônimo será executado no thread principal. Há também o VCLSync que retorna quando o método anônimo foi chamado no thread principal.

Pool de threads em AsyncCalls

De volta à minha tarefa "verificação de arquivo": ao alimentar (em um loop for) o pool de threads asynccalls com uma série de TAsyncCalls. Chamadas Invoke (), as tarefas serão adicionadas ao pool interno e serão executadas "quando chegar a hora" (quando as chamadas adicionadas anteriormente tiverem terminado).

Aguarde a conclusão de todos os IAsyncCalls

A função AsyncMultiSync definida em asnyccalls aguarda a conclusão das chamadas assíncronas (e de outros identificadores). Existem alguns sobrecarregado maneiras de chamar AsyncMultiSync, e aqui está a mais simples:

função AsyncMultiSync (const Lista: matriz de IAsyncCall; WaitAll: Boolean = True; Milissegundos: Cardinal = INFINITO): Cardeal; 

Se eu quiser "esperar tudo" implementado, preciso preencher uma matriz de IAsyncCall e executar o AsyncMultiSync em fatias de 61.

My AsnycCalls Helper

Aqui está um pedaço do TAsyncCallsHelper:

AVISO: código parcial! (código completo disponível para download)
usa AsyncCalls;
tipo
TIAsyncCallArray = matriz de IAsyncCall;
TIAsyncCallArrays = matriz de TIAsyncCallArray;
TAsyncCallsHelper = classe
privado
fTasks: TIAsyncCallArrays;
propriedade Tarefas: TIAsyncCallArrays ler f Tarefas;
público
procedimento AddTask (const chamada: IAsyncCall);
procedimento WaitAll;
fim;
AVISO: código parcial!
procedimento TAsyncCallsHelper. WaitAll;
var
i: inteiro;
início
para i: = Alto (Tarefas) até Baixo (tarefas) Faz
início
AsyncCalls. AsyncMultiSync (Tarefas [i]);
fim;
fim;

Dessa forma, posso "esperar tudo" em blocos de 61 (MAXIMUM_ASYNC_WAIT_OBJECTS) - ou seja, aguardando matrizes de IAsyncCall.

Com o exposto acima, meu código principal para alimentar o pool de threads se parece com:

procedimento TAsyncCallsForm.btnAddTasksClick (Sender: TObject);
const
nrItems = 200;
var
i: inteiro;
início
asyncHelper. MaxThreads: = 2 * Sistema. CPUCount;
ClearLog ('iniciando');
para i: = 1 para nrItems Faz
início
asyncHelper. AddTask (TAsyncCalls. Invoke (AsyncMethod, i, Random (500)));
fim;
Log ('tudo dentro');
// espera tudo
//asyncHelper.WaitAll;
// ou permita o cancelamento de tudo que não foi iniciado clicando no botão "Cancelar tudo":

enquanto não asyncHelper. Tudo terminado Faz Inscrição. ProcessMessages;
Log ('concluído');
fim;

Cancelar tudo? - Tem que mudar o AsyncCalls.pas :(

Eu também gostaria de ter uma maneira de "cancelar" as tarefas que estão no pool, mas estão aguardando sua execução.

Infelizmente, o AsyncCalls.pas não fornece uma maneira simples de cancelar uma tarefa depois de adicionada ao pool de threads. Não há IAsyncCall. Cancelar ou IAsyncCall. DontDoIfNotAlreadyExecuting ou IAsyncCall. Não se preocupe comigo.

Para que isso funcionasse, tive que alterar o AsyncCalls.pas, tentando alterá-lo o menos possível. que quando Andy lança uma nova versão, só preciso adicionar algumas linhas para ter minha ideia "Cancelar tarefa" trabalhando.

Aqui está o que eu fiz: Adicionei um "procedimento Cancel" ao IAsyncCall. O procedimento Cancelar define o campo "FCancelled" (adicionado) que é verificado quando o pool está prestes a começar a executar a tarefa. Eu precisava alterar um pouco o IAsyncCall. Concluído (para que os relatórios de chamadas sejam concluídos mesmo quando cancelados) e o TAsyncCall. Procedimento InternExecuteAsyncCall (para não executar a chamada se ela tiver sido cancelada).

Você pode usar WinMerge para localizar facilmente diferenças entre o asynccall.pas original de Andy e minha versão alterada (incluída no download).

Você pode baixar o código fonte completo e explorar.

Confissão

AVISO PRÉVIO! :)

o CancelInvocation O método impede que o AsyncCall seja chamado. Se o AsyncCall já estiver processado, uma chamada para CancelInvocation não terá efeito e a função Canceled retornará False, pois o AsyncCall não foi cancelado.
o Cancelado O método retornará True se o AsyncCall tiver sido cancelado por CancelInvocation.
o Esqueço O método desvincula a interface IAsyncCall do AsyncCall interno. Isso significa que, se a última referência à interface IAsyncCall desaparecer, a chamada assíncrona ainda será executada. Os métodos da interface lançam uma exceção se forem chamados após chamar Forget. A função assíncrona não deve chamar o encadeamento principal porque pode ser executado após o TThread. O mecanismo de sincronização / fila foi desligado pela RTL, o que pode causar um bloqueio morto.

Observe, no entanto, que você ainda pode se beneficiar do meu AsyncCallsHelper se precisar aguardar que todas as chamadas assíncronas terminem com "asyncHelper. WaitAll "; ou se você precisar "Cancelar tudo".

instagram story viewer