Esempio di pool di thread Delphi utilizzando AsyncCalls

Questo è il mio prossimo progetto di test per vedere quale libreria di threading per Delphi mi farebbe meglio per il mio compito di "scansione dei file" che vorrei elaborare in più thread / in un pool di thread.

Per ripetere il mio obiettivo: trasformare la mia "scansione di file" sequenziale di 500-2000 + file dall'approccio non thread a uno thread. Non dovrei avere 500 thread in esecuzione contemporaneamente, quindi vorrei utilizzare un pool di thread. Un pool di thread è una classe simile a una coda che alimenta un numero di thread in esecuzione con l'attività successiva dalla coda.

Il primo tentativo (molto semplice) è stato fatto semplicemente estendendo la classe TThread e implementando il metodo Execute (il mio parser di stringhe di thread).

Poiché Delphi non ha implementato una classe di pool di thread pronta all'uso, nel mio secondo tentativo ho provato a utilizzare OmniThreadLibrary di Primoz Gabrijelcic.

OTL è fantastico, ha miliardi di modi per eseguire un'attività in background, una strada da percorrere se si desidera avere un approccio "ignora e dimentica" per consegnare l'esecuzione filettata di parti del codice.

instagram viewer

AsyncCalls di Andreas Hausladen

Nota: ciò che segue sarebbe più facile da seguire se si scarica per la prima volta il codice sorgente.

Durante l'esplorazione di altri modi per eseguire alcune delle mie funzioni in modo thread, ho deciso di provare anche l'unità "AsyncCalls.pas" sviluppata da Andreas Hausladen. Andy AsyncCalls: chiamate di funzione asincrone unit è un'altra libreria che uno sviluppatore di Delphi può utilizzare per alleviare la sofferenza dell'implementazione dell'approccio threaded all'esecuzione di un codice.

Dal blog di Andy: Con AsyncCalls puoi eseguire più funzioni contemporaneamente e sincronizzarle in ogni punto della funzione o del metodo che le ha avviate... L'unità AsyncCalls offre una varietà di prototipi di funzioni per chiamare funzioni asincrone... Implementa un pool di thread! L'installazione è semplicissima: basta usare le asincrone da una qualsiasi delle unità e si ha accesso immediato a cose come "esegui in un thread separato, sincronizza l'interfaccia utente principale, attendi fino al termine".

Oltre alle AsyncCalls gratuite (licenza MPL), Andy pubblica spesso anche le proprie correzioni per l'IDE Delphi come "Delphi Speed ​​Up" e "DDevExtensions"Sono sicuro che ne hai sentito parlare (se non lo usi già).

AsyncCalls In Action

In sostanza, tutte le funzioni di AsyncCall restituiscono un'interfaccia IAsyncCall che consente di sincronizzare le funzioni. IAsnycCall espone i seguenti metodi:

 //v 2.98 di asynccalls.pas
IAsyncCall = interfaccia
// attende fino al termine della funzione e restituisce il valore restituito
funzione Sync: Integer;
// restituisce True al termine della funzione asincronica
funzione finita: booleana;
// restituisce il valore restituito della funzione asincrona, quando Finished è TRUE
funzione ReturnValue: intero;
// dice ad AsyncCalls che la funzione assegnata non deve essere eseguita nell'attuale threa
procedura ForceDifferentThread;
fine;

Ecco una chiamata di esempio a un metodo che prevede due parametri interi (che restituisce una IAsyncCall):

 TAsyncCalls. Invoke (AsyncMethod, i, Random (500));
funzione TAsyncCallsForm. AsyncMethod (taskNr, sleepTime: integer): intero;
inizio
risultato: = sleepTime;
Sleep (sleepTime);
TAsyncCalls. VCLInvoke (
procedura
inizio
Log (Format ('done> nr:% d / task:% d / slept:% d', [tasknr, asyncHelper. TaskCount, sleepTime]));
fine);
fine;

The TAsyncCalls. VCLInvoke è un modo per eseguire la sincronizzazione con il thread principale (il thread principale dell'applicazione - l'interfaccia utente dell'applicazione). VCLInvoke ritorna immediatamente. Il metodo anonimo verrà eseguito nel thread principale. C'è anche VCLSync che ritorna quando è stato chiamato il metodo anonimo nel thread principale.

Pool di thread in AsyncCalls

Torna al mio compito di "scansione dei file": durante l'alimentazione (in un ciclo for) il pool di thread asincrono con una serie di TAsyncCalls. Richiamare () le chiamate, le attività verranno aggiunte all'interno del pool e verranno eseguite "quando arriva il momento" (al termine delle chiamate precedentemente aggiunte).

Attendere il completamento di tutte le chiamate IAsyncCalls

La funzione AsyncMultiSync definita in asnyccalls attende il completamento delle chiamate asincrone (e di altri handle). Ci sono alcuni stracarico modi per chiamare AsyncMultiSync, ed ecco il più semplice:

funzione AsyncMultiSync (const Elenco: matrice di IAsyncCall; WaitAll: Boolean = True; Millisecondi: Cardinale = INFINITO): Cardinale; 

Se voglio avere "wait all" implementato, devo compilare un array di IAsyncCall ed eseguire AsyncMultiSync in sezioni di 61.

My AsnycCalls Helper

Ecco un pezzo del TAsyncCallsHelper:

ATTENZIONE: codice parziale! (codice completo disponibile per il download)
usi AsyncCalls;
genere
TIAsyncCallArray = matrice di IAsyncCall;
TIAsyncCallArrays = matrice di TIAsyncCallArray;
TAsyncCallsHelper = classe
privato
fTasks: TIAsyncCallArrays;
proprietà Attività: TIAsyncCallArrays leggere fTasks;
pubblico
procedura addTask (const chiama: IAsyncCall);
procedura WaitAll;
fine;
ATTENZIONE: codice parziale!
procedura TAsyncCallsHelper. WaitAll;
var
i: numero intero;
inizio
per i: = Alto (Attività) giù verso Basso (attività) fare
inizio
AsyncCalls. AsyncMultiSync (Task [i]);
fine;
fine;

In questo modo posso "aspettare tutto" in blocchi di 61 (MAXIMUM_ASYNC_WAIT_OBJECTS), ovvero in attesa di array di IAsyncCall.

Con quanto sopra, il mio codice principale per alimentare il pool di thread è simile a:

procedura TAsyncCallsForm.btnAddTasksClick (Mittente: TObject);
const
nrItems = 200;
var
i: numero intero;
inizio
asyncHelper. MaxThreads: = 2 * Sistema. CPUCount;
Clearlog ( 'partire');
per i: = 1 a nrItems fare
inizio
asyncHelper. AddTask (TAsyncCalls. Invoke (AsyncMethod, i, Random (500)));
fine;
Log ('all in');
// aspetta tutto
//asyncHelper.WaitAll;
// o consenti l'annullamento di tutto non avviato facendo clic sul pulsante "Annulla tutto":

mentre NON asyncHelper. Tutto finito fare Applicazione. ProcessMessages;
Log ( 'finito');
fine;

Cancella tutto? - Devono cambiare AsyncCalls.pas :(

Vorrei anche avere un modo di "annullare" quelle attività che si trovano nel pool ma che attendono la loro esecuzione.

Sfortunatamente, AsyncCalls.pas non fornisce un modo semplice per annullare un'attività una volta che è stata aggiunta al pool di thread. Non c'è IAsyncCall. Annulla o IAsyncCall. DontDoIfNotAlreadyExecuting o IAsyncCall. NeverMindMe.

Perché questo funzioni ho dovuto cambiare AsyncCalls.pas provando a modificarlo il meno possibile - così che quando Andy rilascia una nuova versione, devo solo aggiungere alcune righe per avere la mia idea di "Annulla attività" Lavorando.

Ecco cosa ho fatto: ho aggiunto una "procedura Annulla" a IAsyncCall. La procedura Annulla imposta il campo "FCancelled" (aggiunto) che viene verificato quando il pool sta per iniziare l'esecuzione dell'attività. Ho dovuto modificare leggermente IAsyncCall. Terminato (in modo che un rapporto di chiamata sia terminato anche quando annullato) e la chiamata TAsync. Procedura InternExecuteAsyncCall (non eseguire la chiamata se è stata annullata).

Puoi usare WinMerge per individuare facilmente le differenze tra asynccall.pas originale di Andy e la mia versione modificata (inclusa nel download).

Puoi scaricare il codice sorgente completo ed esplorare.

Confessione

AVVISO! :)

Il CancelInvocation Il metodo interrompe la chiamata all'AsyncCall. Se AsyncCall è già stato elaborato, una chiamata a CancelInvocation non ha alcun effetto e la funzione Annullata restituirà False poiché AsyncCall non è stata annullata.
Il Annullato Il metodo restituisce True se AsyncCall è stato annullato da CancelInvocation.
Il Dimenticare Il metodo scollega l'interfaccia IAsyncCall dall'AsyncCall interno. Ciò significa che se l'ultimo riferimento all'interfaccia IAsyncCall scompare, la chiamata asincrona verrà comunque eseguita. I metodi dell'interfaccia genereranno un'eccezione se chiamati dopo aver chiamato Dimentica. La funzione asincrona non deve chiamare nel thread principale perché potrebbe essere eseguita dopo il TThread. Il meccanismo di sincronizzazione / coda è stato disattivato dall'RTL e ciò può causare un blocco morto.

Nota, tuttavia, che puoi comunque beneficiare del mio AsyncCallsHelper se devi aspettare che tutte le chiamate asincrone finiscano con "asyncHelper. WaitAll "; o se è necessario "Annulla tutto".

instagram story viewer