Delphi Thread Pool Exempel med hjälp av AsyncCalls

Detta är mitt nästa testprojekt för att se vilket trådbibliotek för Delphi som passar mig bäst för min "filskanning" -uppgift som jag vill behandla i flera trådar / i en trådpool.

För att upprepa mitt mål: omvandla min sekventiella "filskanning" på 500-2000 + filer från den icke-gängade metoden till en gängad. Jag borde inte ha 500 trådar på en gång, så jag skulle vilja använda en trådpool. En trådpool är en köliknande klass som matar ett antal löpande trådar med nästa uppgift från kön.

Det första (mycket grundläggande) försöket gjordes genom att helt enkelt utöka TThread-klassen och implementera Execute-metoden (min threaded string parser).

Eftersom Delphi inte har en trådpoolklasse implementerad ur rutan har jag i mitt andra försök försökt använda OmniThreadLibrary av Primoz Gabrijelcic.

OTL är fantastiskt, har zillion sätt att köra en uppgift i en bakgrund, ett sätt att gå om du vill ha "eld-och-glöm" -metod för att lämna gängad exekvering av delar av din kod.

AsyncCalls av Andreas Hausladen

instagram viewer
Obs: Det följande är lättare att följa om du först laddar ner källkoden.

Medan jag undersöker fler sätt att få några av mina funktioner utförda på ett gängat sätt har jag beslutat att också prova "AsyncCalls.pas" -enheten utvecklad av Andreas Hausladen. Andys AsyncCalls - Asynkronfunktionssamtal enheten är ett annat bibliotek som en Delphi-utvecklare kan använda för att underlätta smärtan av att implementera gängat tillvägagångssätt för att utföra någon kod.

Från Andy blogg: Med AsyncCalls kan du köra flera funktioner samtidigt och synkronisera dem vid varje punkt i den funktion eller metod som startade dem... Enheten AsyncCalls erbjuder en mängd olika prototyper för att kalla asynkrona funktioner... Den implementerar en trådpool! Installationen är superenkel: använd bara asynccalls från någon av dina enheter och du har direkt tillgång till saker som "kör i en separat tråd, synkronisera huvudgränssnittet, vänta tills det är klart".

Förutom den fria att använda (MPL-licens) AsyncCalls publicerar Andy också ofta sina egna korrigeringar för Delphi IDE som "Delphi påskynda"och"DDevExtensions"Jag är säker på att du har hört talas om (om du inte redan använder det).

AsyncCalls In Action

I huvudsak returnerar alla AsyncCall-funktioner ett IAsyncCall-gränssnitt som gör det möjligt att synkronisera funktionerna. IAsnycCall avslöjar följande metoder:

 //v 2.98 av asynccalls.pas
IAsyncCall = gränssnitt
// väntar tills funktionen är klar och returnerar returvärdet
funktion Synk: heltal;
// returnerar sant när asynkronfunktionen är klar
funktion Färdig: Boolean;
// returnerar asynkronfunktionens returvärde, när Finished är SANT
funktion ReturnValue: Heltal;
// berättar för AsyncCalls att den tilldelade funktionen inte får köras i den aktuella tråden
procedur ForceDifferentThread;
slutet;

Här är ett exempel på en metod till en metod som förväntar sig två heltalsparametrar (returnerar ett IAsyncCall):

 TAsyncCalls. Åkalla (AsyncMethod, i, Slumpmässigt (500));
fungera TAsyncCallsForm. AsyncMethod (taskNr, sleepTime: heltal): heltal;
Börja
resultat: = sleepTime;
Sleep (sleepTime);
TAsyncCalls. VCLInvoke (
procedur
Börja
Log (Format ('gjort> nr:% d / uppgifter:% d / sov:% d', [tasknr, asyncHelper). TaskCount, sleepTime]));
slutet);
slutet;

TAsyncCalls. VCLInvoke är ett sätt att synkronisera med din huvudtråd (applikationens huvudtråd - ditt applikationsanvändargränssnitt). VCLInvoke återgår omedelbart. Den anonyma metoden kommer att köras i huvudtråden. Det finns också VCLSync som kommer tillbaka när den anonyma metoden anropades i huvudtråden.

Trådpool i AsyncCalls

Tillbaka till min "filskanning" -uppgift: när du matar in (i en för-loop) asynccalls trådpool med en serie TAsyncCalls. Anropa () samtal, uppgifterna läggs till i interna poolen och kommer att köras "när tiden kommer" (när tidigare tillagda samtal har avslutats).

Vänta alla IAsyncCalls till slut

AsyncMultiSync-funktionen som definieras i asnyccalls väntar på att async-samtal (och andra handtag) är slut. Det finns några överbelastad sätt att ringa AsyncMultiSync, och här är de enklaste:

fungera AsyncMultiSync (const Lista: matris av IAsyncCall; WaitAll: Boolean = True; Millisekunder: kardinal = INFINITE): kardinal; 

Om jag vill ha "vänta allt" implementerat måste jag fylla i en matris med IAsyncCall och göra AsyncMultiSync i skivor av 61.

Min AsnycCalls Helper

Här är en bit av TAsyncCallsHelper:

VARNING: partiell kod! (fullständig kod tillgänglig för nedladdning)
användningar AsyncCalls;
typ
TIAsyncCallArray = matris av IAsyncCall;
TIAsyncCallArrays = matris av TIAsyncCallArray;
TAsyncCallsHelper = klass
privat
fTasks: TIAsyncCallArrays;
fast egendom Uppgifter: TIAsyncCallArrays läsa fTasks;
offentlig
procedur AddTask (const samtal: IAsyncCall);
procedur WaitAll;
slutet;
VARNING: partiell kod!
procedur TAsyncCallsHelper. WaitAll;
var
i: heltal;
Börja
för i: = Hög (Uppgifter) ner till Låg (uppgifter) do
Börja
AsyncCalls. AsyncMultiSync (Uppgifter [i]);
slutet;
slutet;

På det här sättet kan jag "vänta alla" i bitar på 61 (MAXIMUM_ASYNC_WAIT_OBJECTS) - dvs vänta på matriser av IAsyncCall.

Med ovanstående ser min huvudkod för matning av trådpoolen ut:

procedur TAsyncCallsForm.btnAddTasksClick (avsändare: TObject);
const
nrItems = 200;
var
i: heltal;
Börja
asyncHelper. MaxThreads: = 2 * System. CPUCount;
ClearLog ( 'start');
för i: = 1 till nrItems do
Börja
asyncHelper. AddTask (TAsyncCalls. Åkalla (AsyncMethod, i, Slumpmässigt (500)));
slutet;
Logg ('allt in');
// vänta alla
//asyncHelper.WaitAll;
// eller tillåt att avbryta alla inte startade genom att klicka på "Avbryt alla" -knappen:

Medan inte asyncHelper. AllFinished do Ansökan. ProcessMessages;
Log ( 'färdiga');
slutet;

Avbryta alla? - Måste ändra AsyncCalls.pas :(

Jag skulle också vilja ha ett sätt att "avbryta" de uppgifter som finns i poolen men väntar på att de ska genomföras.

Tyvärr ger AsyncCalls.pas inte ett enkelt sätt att avbryta en uppgift när den har lagts till i trådpoolen. Det finns inget IAsyncCall. Avbryt eller IAsyncCall. DontDoIfNotAlreadyExecuting eller IAsyncCall. Bry er inte om mig.

För att detta skulle fungera var jag tvungen att ändra AsyncCalls.pas genom att försöka förändra det så mindre som möjligt - så att när Andy släpper en ny version måste jag bara lägga till några rader för att få min "Avbryt uppgift" -idé arbetssätt.

Här är vad jag gjorde: Jag har lagt till en "procedur Avbryt" till IAsyncCall. Avbryt-proceduren ställer in fältet "FCancelled" (tillagd) som kontrolleras när poolen håller på att börja utföra uppgiften. Jag behövde ändra IAsyncCall något. Färdig (så att ett samtal rapporterar slutfört även vid avbokning) och TAsyncCall. InternExecuteAsyncCall-procedur (för att inte utföra samtalet om det har avbrutits).

Du kan använda WinMerge för att enkelt hitta skillnader mellan Andy ursprungliga asynccall.pas och min förändrade version (ingår i nedladdningen).

Du kan ladda ner hela källkoden och utforska.

Bekännelse

LÄGGA MÄRKE TILL! :)

De CancelInvocation metoden hindrar AsyncCall från att åberopas. Om AsyncCall redan har behandlats har ett samtal till CancelInvocation ingen effekt och funktionen Avbruten kommer att returnera False eftersom AsyncCall inte avbröts.
De Inställt metoden returnerar sant om AsyncCall avbröts av CancelInvocation.
De Glömma metoden kopplar bort IAsyncCall-gränssnittet från det interna AsyncCall. Detta betyder att om den sista referensen till IAsyncCall-gränssnittet är borta kommer det asynkrona samtalet fortfarande att köras. Gränssnittets metoder kommer att kasta ett undantag om de kallas efter att ha ringt Glöm. Async-funktionen får inte ringa in i huvudtråden eftersom den kan köras efter TThread. Synkroniserings- / kömekanismen stängdes av RTL vad som kan orsaka dödlås.

Observera dock att du fortfarande kan dra nytta av min AsyncCallsHelper om du behöver vänta tills alla async-samtal slutar med "asyncHelper. WaitAll "; eller om du behöver "Avbryt alla".

instagram story viewer