Dit is mijn volgende testproject om te zien welke threadingbibliotheek voor Delphi het beste bij mij past voor mijn "bestand scannen" -taak die ik in meerdere threads / in een thread-pool wil verwerken.
Om mijn doel te herhalen: transformeer mijn opeenvolgende "bestandsscanning" van 500-2000 + bestanden van de niet-threaded aanpak naar een threaded aanpak. Ik zou niet 500 threads tegelijk moeten hebben, dus zou ik een thread pool willen gebruiken. Een thread pool is een wachtrij-achtige klasse die een aantal lopende threads voedt met de volgende taak uit de wachtrij.
De eerste (zeer eenvoudige) poging werd gedaan door eenvoudig de TThread-klasse uit te breiden en de Execute-methode te implementeren (mijn threaded string parser).
Aangezien Delphi geen threadpool-klasse direct uit de doos heeft geïmplementeerd, heb ik in mijn tweede poging geprobeerd OmniThreadLibrary van Primoz Gabrijelcic te gebruiken.
OTL is fantastisch, heeft ontelbare manieren om een taak op de achtergrond uit te voeren, een manier om te gaan als je een "vuur-en-vergeet" -benadering wilt voor het uitvoeren van threaded uitvoering van stukken van uw code.
Opmerking: wat volgt, is gemakkelijker te volgen als u eerst de broncode downloadt.
Tijdens het verkennen van meer manieren om sommige van mijn functies op een threaded manier uit te voeren, heb ik besloten om ook de "AsyncCalls.pas" -eenheid te proberen, ontwikkeld door Andreas Hausladen. Andy's AsyncCalls - Asynchrone functie-aanroep is een andere bibliotheek die een Delphi-ontwikkelaar kan gebruiken om de pijn van het implementeren van een threaded aanpak voor het uitvoeren van een code te verlichten.
Van Andy's blog: Met AsyncCalls kunt u meerdere functies tegelijkertijd uitvoeren en ze op elk punt in de functie of methode waarmee ze zijn gestart synchroniseren ... De AsyncCalls biedt een verscheidenheid aan functieprototypes om asynchrone functies aan te roepen ... Het implementeert een thread pool! De installatie is supereenvoudig: gebruik gewoon asynccalls van al je units en je hebt direct toegang tot dingen als "voer uit in een aparte thread, synchroniseer de hoofdinterface, wacht tot je klaar bent".
Naast de gratis te gebruiken (MPL-licentie) AsyncCalls, publiceert Andy ook regelmatig zijn eigen fixes voor de Delphi IDE zoals "Delphi Speed Up" en "DDevExtensions" Ik weet zeker dat je hebt gehoord (als je dit al niet gebruikt).
In essentie retourneren alle AsyncCall-functies een IAsyncCall-interface waarmee de functies kunnen worden gesynchroniseerd. IAsnycCall onthult de volgende methoden:
//v 2.98 van asynccalls.pas
IAsyncCall = interface
// wacht totdat de functie is voltooid en retourneert de retourwaarde
functie Sync: geheel getal;
// retourneert True wanneer de asynchron-functie is voltooid
functie Voltooid: Boolean;
// retourneert de retourwaarde van de asynchrone functie, wanneer Voltooid WAAR is
functie ReturnValue: Geheel getal;
// vertelt AsyncCalls dat de toegewezen functie niet mag worden uitgevoerd in de huidige threa
procedure ForceDifferentThread;
einde;
Hier is een voorbeeld van een aanroep van een methode die twee geheel-getalparameters verwacht (een IAsyncCall terug):
TAsyncCalls.Invoke (AsyncMethod, i, Random (500));
functie TAsyncCallsForm.AsyncMethod (taskNr, sleepTime: integer): integer;
beginnen
resultaat: = sleepTime;
Sleep (slaaptijd);
TAsyncCalls.VCLInvoke (
procedure
beginnen
Logboek (formaat ('gedaan> nr:% d / taken:% d / geslapen:% d', [tasknr, asyncHelper.TaskCount, sleepTime]));
einde);
einde;
De TAsyncCalls.VCLInvoke is een manier om te synchroniseren met uw hoofdthread (de hoofdthread van de applicatie - de gebruikersinterface van uw applicatie). VCLInvoke keert onmiddellijk terug. De anonieme methode wordt uitgevoerd in de hoofdthread. Er is ook VCLSync die terugkeert wanneer de anonieme methode in de hoofdthread werd aangeroepen.
Terug naar mijn "bestand scannen" taak: bij het voeden (in een for-lus) van de asynccalls thread pool met reeks TAsyncCalls.Invoke () aanroepen, zullen de taken worden toegevoegd aan de interne pool en worden uitgevoerd "wanneer de tijd komt" ( wanneer eerder toegevoegde oproepen zijn beëindigd).
De AsyncMultiSync-functie die in asnyccalls is gedefinieerd, wacht tot de async-aanroepen (en andere grepen) zijn voltooid. Er zijn een paar overbelaste manieren om AsyncMultiSync aan te roepen, en hier is de eenvoudigste:
functie AsyncMultiSync (const Lijst: reeks van IAsyncCall; WaitAll: Boolean = True; Milliseconden: kardinaal = INFINITE): kardinaal;
Als ik "wacht alles" geïmplementeerd wil hebben, moet ik een reeks IAsyncCall invullen en AsyncMultiSync doen in plakjes van 61.
Hier is een stuk van de TAsyncCallsHelper:
WAARSCHUWING: gedeeltelijke code! (volledige code beschikbaar om te downloaden)
toepassingen AsyncCalls;
type
TIAsyncCallArray = reeks van IAsyncCall;
TIAsyncCallArrays = reeks van TIAsyncCallArray;
TAsyncCallsHelper = klasse
privaat
fTaken: TIAsyncCallArrays;
eigendom Taken: TIAsyncCallArrays lezen fTasks;
openbaar
procedure Voeg taak toe(const oproep: IAsyncCall);
procedure WaitAll;
einde;
WAARSCHUWING: gedeeltelijke code!
procedure TAsyncCallsHelper.WaitAll;
var
i: geheel getal;
beginnen
voor i: = High (Taken) downto Laag (Taken) Doen
beginnen
AsyncCalls.AsyncMultiSync (Taken [i]);
einde;
einde;
Op deze manier kan ik "alles wachten" in brokken van 61 (MAXIMUM_ASYNC_WAIT_OBJECTS) - d.w.z. wachten op arrays van IAsyncCall.
Met het bovenstaande ziet mijn hoofdcode om de threadpool te voeden er als volgt uit:
procedure TAsyncCallsForm.btnAddTasksClick (Sender: TObject);
const
nrItems = 200;
var
i: geheel getal;
beginnen
asyncHelper.MaxThreads: = 2 * System.CPUCount;
ClearLog ( 'start');
voor i: = 1 tot nrItems Doen
beginnen
asyncHelper.AddTask (TAsyncCalls.Invoke (AsyncMethod, i, Random (500)));
einde;
Log ('alles in');
// wacht allemaal
//asyncHelper.WaitAll;
// of sta alle annulering toe die niet is gestart door op de knop "Alles annuleren" te klikken:
terwijl NIET asyncHelper.AllFinished Doen Application.ProcessMessages;
Log ( 'af');
einde;
Ik zou ook een manier willen hebben om die taken die in de pool zitten te "annuleren" maar wachten op hun uitvoering.
Helaas biedt de AsyncCalls.pas geen eenvoudige manier om een taak te annuleren nadat deze is toegevoegd aan de threadpool. Er is geen IAsyncCall.Cancel of IAsyncCall.DontDoIfNotAlreadyExecuting of IAsyncCall.NeverMindMe.
Om dit te laten werken, moest ik de AsyncCalls.pas wijzigen door deze zo min mogelijk te proberen te wijzigen - zodat wanneer Andy een nieuwe versie uitbrengt, ik slechts een paar regels moet toevoegen om mijn idee "taak annuleren" te laten werken.
Dit heb ik gedaan: ik heb een "procedure Annuleren" toegevoegd aan de IAsyncCall. Met de procedure Annuleren wordt het veld "FCancelled" (toegevoegd) ingesteld dat wordt aangevinkt wanneer de pool op het punt staat de taak uit te voeren. Ik moest de IAsyncCall.Finished (zodat een gespreksrapport ook afliep wanneer deze werd geannuleerd) en de TAsyncCall.InternExecuteAsyncCall-procedure enigszins wijzigen (de oproep niet uitvoeren als deze is geannuleerd).
U kunt WinMerge gebruiken om eenvoudig verschillen te vinden tussen de originele asynccall.pas van Andy en mijn gewijzigde versie (inbegrepen in de download).
U kunt de volledige broncode downloaden en verkennen.
De CancelInvocation methode voorkomt dat de AsyncCall wordt aangeroepen. Als de AsyncCall al is verwerkt, heeft een oproep naar CancelInvocation geen effect en zal de functie Cancelled False retourneren omdat de AsyncCall niet is geannuleerd.
De Geannuleerd methode retourneert True als de AsyncCall is geannuleerd door CancelInvocation.
De Vergeten methode koppelt de IAsyncCall-interface los van de interne AsyncCall. Dit betekent dat als de laatste verwijzing naar de IAsyncCall-interface verdwenen is, de asynchrone oproep nog steeds wordt uitgevoerd. De methoden van de interface genereren een uitzondering als ze worden aangeroepen nadat Forget is aangeroepen. De async-functie mag niet de hoofdthread oproepen omdat deze kan worden uitgevoerd nadat de TThread.Synchronize / Queue-mechanisme is afgesloten door de RTL, wat een dead lock kan veroorzaken.
Merk echter op dat u nog steeds kunt profiteren van mijn AsyncCallsHelper als u moet wachten tot alle async-oproepen zijn voltooid met "asyncHelper.WaitAll"; of als je "Alles Annuleren" moet.