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 klassen TThread och implementera exekveringsmetoden (min gängade strängparter).

Eftersom Delphi inte har en trådpoolklasse implementerad ur lådan 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 bakgrunden, ett sätt att gå om du vill ha "eld-och-glöm" -metod för att lämna gängad exekvering av bitar av din kod.

AsyncCalls av Andreas Hausladen

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

Medan jag utforskade 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 - Asynkron funktionssamtalsenhet ä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 köra en kod.

Från Andy blogg: Med AsyncCalls kan du köra flera funktioner samtidigt och synkronisera dem vid varje punkt i funktionen eller metoden som startade dem ... AsyncCalls-enheten erbjuder en mängd olika prototyper för att anropa asynkrona funktioner ... Den implementerar en trådpool! Installationen är superlätt: använd bara asynccalls från någon av dina enheter och du har direkt tillgång till saker som "köra 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 Speed ​​Up" 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.Invoke (AsyncMethod, i, Random (500));
     
     
     
     
fungera TAsyncCallsForm.AsyncMethod (taskNr, sleepTime: heltal): heltal;
Börja
resultat: = sleepTime;
Sömn (sömntid);
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 - applikationsanvändargränssnittet). 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.Invoke () samtal läggs uppgifterna till interna poolen och kommer att köras "när tiden kommer" ( när tidigare tillagda samtal har slutförts).

Vänta alla IAsyncCalls till slut

AsyncMultiSync-funktionen som definieras i asnyccalls väntar på att async-samtal (och andra handtag) är slutförda. Det finns några överbelastade sätt att ringa AsyncMultiSync, och här är det 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åga (aktiviteter) do
Börja
AsyncCalls.AsyncMultiSync (Steg [i]);
slutet;
slutet;

På så sätt kan jag "vänta alla" i bitar av 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.Invoke (AsyncMethod, i, Random (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 Application.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 ingen IAsyncCall.Cancel eller IAsyncCall.DontDoIfNotAlreadyExecuting eller IAsyncCall.NeverMindMe.

För att detta skulle fungera var jag tvungen att ändra AsyncCalls.pas genom att försöka ä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é att fungera.

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 något på IAsyncCall.Finished (så att ett samtal rapporter slutfördes även när det avbröts) och TAsyncCall.InternExecuteAsyncCall-proceduren (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 falskt 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 innebär 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.Synchronize / Queue-mekanismen stängdes av RTL vad kan orsaka dödlås.

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