Delphi Thread Pool-eksempel ved hjælp af AsyncCalls

click fraud protection

Dette er mit næste testprojekt for at se, hvilket trådbibliotek til Delphi der passer mig bedst til min "fil scanning" -opgave, jeg gerne vil behandle i flere tråde / i en trådpool.

For at gentage mit mål: transformer min sekventielle "fil scanning" på 500-2000 + filer fra den ikke-trådede tilgang til en gevind. Jeg skulle ikke have 500 tråde, der kører på én gang, og vil derfor gerne bruge en trådpool. En trådpool er en kølignende klasse, der fodrer et antal kørende tråde med den næste opgave fra køen.

Det første (meget grundlæggende) forsøg blev gjort ved blot at udvide TThread-klassen og implementere Execute-metoden (min threaded string parser).

Da Delphi ikke har en tråd poolklasse implementeret ud af boksen, har jeg i mit andet forsøg prøvet at bruge OmniThreadLibrary af Primoz Gabrijelcic.

OTL er fantastisk, har zillion måder at udføre en opgave i en baggrund, en vej at gå, hvis du vil have en "brand-og-glem" tilgang til at udlevere gevind udførelse af stykker af din kode.

AsyncCalls af Andreas Hausladen

instagram viewer
Bemærk: Det følgende ville være lettere at følge, hvis du først downloader kildekoden.

Mens jeg udforskede flere måder at få nogle af mine funktioner udført på med gevind, besluttede jeg også at prøve "AsyncCalls.pas" -enheden udviklet af Andreas Hausladen. Andys AsyncCalls - opkald til asynkron funktion enhed er et andet bibliotek, som en Delphi-udvikler kan bruge til at lette smerten ved at implementere gevindtilgang til udførelse af nogle kode.

Fra Andy blog: Med AsyncCalls kan du udføre flere funktioner på samme tid og synkronisere dem på hvert punkt i den funktion eller metode, der startede dem... Enheden AsyncCalls tilbyder en række funktionsprototyper til at kalde asynkrone funktioner... Det implementerer en trådpulje! Installationen er super nem: Brug bare asyncaller fra enhver af dine enheder, og du har øjeblikkelig adgang til ting som "kør i en separat tråd, synkroniser hovedgrænsefladen, vent til den er færdig".

Udover den gratis at bruge (MPL-licens) AsyncCalls, udgiver Andy også ofte sine egne rettelser til Delphi IDE som "Delphi fremskynder"og"DDevExtensions"Jeg er sikker på, at du har hørt om (hvis du ikke allerede bruger det).

AsyncCalls i aktion

I det væsentlige returnerer alle AsyncCall-funktioner en IAsyncCall-interface, der gør det muligt at synkronisere funktionerne. IAsnycCall afslører følgende metoder:

 //v 2.98 af asynccalls.pas
IAsyncCall = interface
// venter til funktionen er færdig og returnerer returværdien
funktion Synkronisering: Heltal;
// returnerer sandt, når asynkronfunktionen er færdig
funktion Færdig: Boolsk;
// returnerer asynkronfunktionens returneringsværdi, når Færdig er SAND
funktion ReturnValue: Heltal;
// fortæller AsyncCalls, at den tildelte funktion ikke må udføres i den aktuelle tråd
procedure ForceDifferentThread;
ende;

Her er et eksempel på et opkald til en metode, der forventer to heltalparametre (returnering af et IAsyncCall):

 TAsyncCalls. Påkald (AsyncMethod, i, Tilfældig (500));
fungere TAsyncCallsForm. AsyncMethod (taskNr, sleepTime: heltal): heltal;
begynde
resultat: = sleepTime;
Sleep (sleepTime);
TAsyncCalls. VCLInvoke (
procedure
begynde
Log (Format ('gjort> nr:% d / opgaver:% d / sov:% d', [tasknr, asyncHelper. TaskCount, sleepTime]));
ende);
ende;

TAsyncCalls. VCLInvoke er en måde at synkronisere med din hovedtråd (applikationens hovedtråd - dit applikationsbrugergrænseflade). VCLInvoke vender øjeblikkeligt tilbage. Den anonyme metode udføres i hovedtråden. Der er også VCLSync, der vender tilbage, når den anonyme metode blev kaldt i hovedtråden.

Trådpulje i AsyncCalls

Tilbage til min "filscanning" -opgave: når du fodrer (i en for-loop) asynccalls trådpuljen med en række TAsyncCalls. Påkald () opkald, opgaverne tilføjes til den interne pool og udføres "når tiden kommer" (når tidligere tilføjede opkald er færdige).

Vent, at alle IAsyncCalls er afsluttet

AsyncMultiSync-funktionen, der er defineret i asnyccalls, venter på, at async-opkaldene (og andre håndtag) er afsluttet. Der er nogle få overbelastet måder at kalde AsyncMultiSync på, og her er den enkleste:

fungere AsyncMultiSync (const Liste: matrix af IAsyncCall; Vent alt: Boolsk = sandt; Millisekunder: Kardinal = INFINITE): Kardinal; 

Hvis jeg vil have "vent alle" implementeret, skal jeg udfylde en række IAsyncCall og gøre AsyncMultiSync i skiver på 61.

Min AsnycCalls-hjælper

Her er et stykke af TAsyncCallsHelper:

ADVARSEL: delkode! (fuld kode tilgængelig til download)
anvendelser AsyncCalls;
type
TIAsyncCallArray = matrix af IAsyncCall;
TIAsyncCallArrays = matrix af TIAsyncCallArray;
TAsyncCallsHelper = klasse
privat
fTasks: TIAsyncCallArrays;
ejendom Opgaver: TIAsyncCallArrays Læs fTasks;
offentlig
procedure AddTask (const opkald: IAsyncCall);
procedure WaitAll;
ende;
ADVARSEL: delkode!
procedure TAsyncCallsHelper. WaitAll;
Var
i: heltal;
begynde
til i: = Høj (Opgaver) ned til Lav (opgaver) gøre
begynde
AsyncCalls. AsyncMultiSync (Opgaver [i]);
ende;
ende;

På denne måde kan jeg "vente alle" i bunker på 61 (MAXIMUM_ASYNC_WAIT_OBJECTS) - dvs. vente på arrays af IAsyncCall.

Med ovenstående ser min hovedkode til fodring af trådpuljen ud:

procedure TAsyncCallsForm.btnAddTasksClick (Afsender: TObject);
const
nrItems = 200;
Var
i: heltal;
begynde
asyncHelper. MaxThreads: = 2 * System. CPUCount;
ClearLog ( 'starter');
til i: = 1 til nrItems gøre
begynde
asyncHelper. AddTask (TAsyncCalls). Påkald (AsyncMethod, i, Tilfældig (500)));
ende;
Log ('alt ind');
// vent alle
//asyncHelper.WaitAll;
// eller tillad at annullere alle ikke startet ved at klikke på knappen "Annuller alle":

mens IKKE asyncHelper. AllFinished gøre Ansøgning. ProcessMessages;
Log ( 'færdig');
ende;

Annuller alle? - Nødt til at ændre AsyncCalls.pas :(

Jeg vil også gerne have en måde at "annullere" de opgaver, der er i puljen, men venter på deres udførelse.

Desværre giver AsyncCalls.pas ikke en enkel måde at annullere en opgave på, når den først er føjet til trådpuljen. Der er ingen IAsyncCall. Annuller eller IAsyncCall. DontDoIfNotAlreadyExecuting eller IAsyncCall. NeverMindMe.

For at dette skulle fungere, var jeg nødt til at ændre AsyncCalls.pas ved at prøve at ændre det så mindre som muligt - så at når Andy frigiver en ny version, skal jeg kun tilføje et par linjer for at få min "Annuller opgave" -idee arbejder.

Her er, hvad jeg gjorde: Jeg har tilføjet en "procedure Annuller" til IAsyncCall. Annulleringsproceduren indstiller feltet "FCancelled" (tilføjet), som bliver kontrolleret, når puljen er ved at begynde at udføre opgaven. Jeg var nødt til at ændre IAsyncCall lidt. Færdig (således at et opkald rapporteres afsluttet, selv når det blev annulleret) og TAsyncCall. InternExecuteAsyncCall-procedure (ikke at udføre opkaldet, hvis det er blevet annulleret).

Du kan bruge WinMerge for nemt at finde forskelle mellem Andy originale asynccall.pas og min ændrede version (inkluderet i download).

Du kan downloade den fulde kildekode og udforske.

Tilståelse

VARSEL! :)

Det CancelInvocation Metoden forhindrer AsyncCall i at blive aktiveret. Hvis AsyncCall allerede er behandlet, har et opkald til CancelInvocation ingen virkning, og funktionen Annulleret vil returnere False, da AsyncCall ikke blev annulleret.
Det Annulleret metoden returnerer sandt, hvis AsyncCall blev annulleret af CancelInvocation.
Det Glemme metoden fjerner linket fra IAsyncCall-interface fra det interne AsyncCall. Dette betyder, at hvis den sidste henvisning til IAsyncCall-grænsefladen er væk, udføres det asynkrone opkald stadig. Grænsefladens metoder vil kaste en undtagelse, hvis det kaldes efter opkald til Forget. Async-funktionen må ikke kalde ind i hovedtråden, fordi den kunne udføres efter TThread. Synkroniserings- / kømekanismen blev lukket af RTL, hvad der kan forårsage en død lås.

Bemærk dog, at du stadig kan drage fordel af min AsyncCallsHelper, hvis du har brug for at vente på, at alle async-opkald afsluttes med "asyncHelper. WaitAll "; eller hvis du har brug for at "Annuller alt".

instagram story viewer