Dit is mijn volgende testproject om te zien welke threads-bibliotheek voor Delphi het beste bij mij past voor mijn "bestandscanning" -taak die ik in meerdere threads / in een threadpool zou willen verwerken.
Om mijn doel te herhalen: transformeer mijn opeenvolgende "bestandsscanning" van 500-2000 + bestanden van de niet-threaded benadering naar een threaded. Ik zou niet 500 threads tegelijk moeten hebben, dus ik zou graag een thread pool willen gebruiken. Een threadpool is een wachtrijachtige klasse die een aantal lopende threads voedt met de volgende taak uit de wachtrij.
De eerste (zeer eenvoudige) poging werd gedaan door simpelweg de TThread-klasse uit te breiden en de Execute-methode te implementeren (mijn threaded string parser).
Aangezien Delphi geen out-of-the-box thread-klasse heeft geïmplementeerd, heb ik bij 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 weg te gaan als je een "fire-and-forget" -aanpak wilt hebben voor het overhandigen van threaded uitvoering van stukken van je code.
AsyncCalls door Andreas Hausladen
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 te laten uitvoeren, heb ik besloten om ook de "AsyncCalls.pas" -eenheid te proberen, ontwikkeld door Andreas Hausladen. Andy AsyncCalls - Asynchrone functieaanroepen unit is een andere bibliotheek die een Delphi-ontwikkelaar kan gebruiken om de pijn van het implementeren van een threaded benadering voor het uitvoeren van bepaalde code te verlichten.
Van Andy's blog: Met AsyncCalls kunt u meerdere functies tegelijkertijd uitvoeren en ze synchroniseren op elk punt in de functie of methode waarmee ze zijn gestart... De AsyncCalls-eenheid biedt een verscheidenheid aan functieprototypes om asynchrone functies aan te roepen... Het implementeert een threadpool! De installatie is supergemakkelijk: gebruik asynccalls van al uw eenheden en u hebt direct toegang tot zaken als "voer uit in een aparte thread, synchroniseer de hoofdinterface, wacht tot het klaar is".
Naast de gratis te gebruiken (MPL-licentie) AsyncCalls, publiceert Andy ook regelmatig zijn eigen fixes voor de Delphi IDE zoals "Delphi versnellen'en'DDevExtensions'Ik weet zeker dat je ervan hebt gehoord (als je dat nog niet hebt gedaan).
AsyncCalls in actie
In wezen 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 geretourneerde waarde
functie Sync: Integer;
// retourneert True wanneer de asynchronisatiefunctie is voltooid
functie Klaar: Boolean;
// retourneert de geretourneerde waarde van de asynchrone functie, wanneer Voltooid WAAR is
functie ReturnValue: Integer;
// 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 parameters voor gehele getallen verwacht (het retourneren van een IAsyncCall):
TAsyncCalls. Aanroepen (AsyncMethod, i, Random (500));
functie TAsyncCallsForm. AsyncMethod (taskNr, sleepTime: integer): geheel getal;
beginnen
resultaat: = sleepTime;
Slaap (sleepTime);
TAsyncCalls. VCLInvoke (
procedure
beginnen
Log (formaat ('gedaan> nr:% d / taken:% d / geslapen:% d', [tasknr, asyncHelper. TaskCount, sleepTime]));
einde);
einde;
De TAsyncCalls. VCLInvoke is een manier om synchronisatie uit te voeren 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 dat terugkeert wanneer de anonieme methode werd aangeroepen in de hoofdthread.
Thread Pool in AsyncCalls
Terug naar mijn taak voor het "scannen van bestanden": bij het invoeren (in een for-lus) van de asynccalls-threadpool met een reeks TAsyncCalls. Invoke () calls, de taken zullen worden toegevoegd aan de interne pool en zullen worden uitgevoerd "wanneer de tijd komt" (wanneer eerder toegevoegde calls zijn beëindigd).
Wacht tot alle IAsyncCalls zijn voltooid
De AsyncMultiSync-functie gedefinieerd in asnyccalls wacht tot de asynchrone aanroepen (en andere handvatten) zijn voltooid. Er zijn een paar overbelast manieren om AsyncMultiSync aan te roepen, en hier is de eenvoudigste:
functie AsyncMultiSync (const Lijst: reeks van IAsyncCall; WaitAll: Boolean = True; Milliseconden: Cardinal = INFINITE): Cardinal;
Als ik "wacht alles" geïmplementeerd wil hebben, moet ik een array van IAsyncCall invullen en AsyncMultiSync in plakjes van 61 uitvoeren.
Mijn AsnycCalls Helper
Hier is een deel 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
fTasks: TIAsyncCallArrays;
eigendom Taken: TIAsyncCallArrays lezen fTasks;
openbaar
procedure Voeg taak toe(const bellen: IAsyncCall);
procedure WaitAll;
einde;
WAARSCHUWING: gedeeltelijke code!
procedure TAsyncCallsHelper. WaitAll;
var
i: geheel getal;
beginnen
voor i: = hoog (taken) beneden Laag (taken) Doen
beginnen
AsyncCalls. AsyncMultiSync (Taken [i]);
einde;
einde;
Op deze manier kan ik "alles wachten" in stukjes 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 eruit als:
procedure TAsyncCallsForm.btnAddTasksClick (Afzender: TObject);
const
nrItems = 200;
var
i: geheel getal;
beginnen
asyncHelper. MaxThreads: = 2 * systeem. CPUCount;
ClearLog ('starten');
voor i: = 1 tot nrItems Doen
beginnen
asyncHelper. AddTask (TAsyncCalls. Aanroepen (AsyncMethod, i, Random (500)));
einde;
Log ('all in');
// wacht allemaal
//asyncHelper.WaitAll;
// of sta het annuleren van alles toe dat niet is gestart door op de knop "Alles annuleren" te klikken:
terwijl NIET asyncHelper. Alles af Doen Toepassing. ProcessMessages;
Log ('afgewerkt');
einde;
Verwijder alles? - Moet de AsyncCalls.pas wijzigen :(
Ik zou ook graag een manier willen hebben om de taken die zich in de pool bevinden 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. Annuleer of IAsyncCall. DontDoIfNotAlreadyExecuting of IAsyncCall. Houdt geen rekening met mij.
Om dit te laten werken, moest ik de AsyncCalls.pas wijzigen door te proberen het zo min mogelijk te wijzigen - dus dat wanneer Andy een nieuwe versie uitbrengt, ik maar een paar regels hoef toe te voegen om mijn idee "Taak annuleren" te krijgen werken.
Dit is wat ik deed: ik heb een "procedure Annuleren" toegevoegd aan de IAsyncCall. De annuleringsprocedure stelt het veld "FCancelled" (toegevoegd) in dat wordt gecontroleerd wanneer de pool op het punt staat de taak uit te voeren. Ik moest de IAsyncCall enigszins wijzigen. Voltooid (zodat een oproeprapport zelfs beëindigd is wanneer geannuleerd) en de TAsyncCall. InternExecuteAsyncCall-procedure (om de oproep niet uit te voeren als deze is geannuleerd).
Je kunt gebruiken WinMerge om gemakkelijk verschillen te vinden tussen Andy's originele asynccall.pas en mijn gewijzigde versie (inbegrepen in de download).
U kunt de volledige broncode downloaden en verkennen.
Bekentenis
MERK OP! :)
De Annuleren Oproep methode voorkomt dat de AsyncCall wordt aangeroepen. Als de AsyncCall al is verwerkt, heeft een aanroep van CancelInvocation geen effect en retourneert de functie Canceled False omdat de AsyncCall niet is geannuleerd.
De Geannuleerd methode retourneert True als de AsyncCall is geannuleerd door CancelInvocation.
De Vergeten methode ontkoppelt de IAsyncCall-interface van de interne AsyncCall. Dit betekent dat als de laatste verwijzing naar de IAsyncCall-interface is verdwenen, de asynchrone aanroep nog steeds wordt uitgevoerd. De methoden van de interface zullen een uitzondering opleveren als ze worden aangeroepen nadat ze Forget hebben aangeroepen. De async-functie mag de hoofdthread niet aanroepen omdat deze na de TThread kan worden uitgevoerd. Het synchronisatie- / wachtrijmechanisme is uitgeschakeld 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 asynchrone aanroepen eindigen met "asyncHelper. WaitAll "; of als je "CancelAll" nodig hebt.