Dies ist mein nächstes Testprojekt, um zu sehen, welche Threading-Bibliothek für Delphi am besten zu mir für meine Aufgabe "Dateiscannen" passt, die ich in mehreren Threads / in einem Thread-Pool verarbeiten möchte.
Um mein Ziel zu wiederholen: Transformiere mein sequentielles "Datei-Scannen" von mehr als 500 Dateien vom nicht-Thread-Ansatz in einen Thread-Ansatz. Ich sollte nicht 500 Threads gleichzeitig ausführen, möchte also einen Thread-Pool verwenden. Ein Thread-Pool ist eine warteschlangenartige Klasse, die eine Reihe von laufenden Threads mit der nächsten Task aus der Warteschlange versorgt.
Der erste (sehr einfache) Versuch wurde unternommen, indem einfach die TThread-Klasse erweitert und die Execute-Methode (mein Thread-String-Parser) implementiert wurde..
Da Delphi keine Thread-Pool-Klasse implementiert hat, habe ich in meinem zweiten Versuch versucht, OmniThreadLibrary von Primoz Gabrijelcic zu verwenden.
OTL ist fantastisch und bietet unzählige Möglichkeiten, um eine Aufgabe im Hintergrund auszuführen. Dies ist eine Möglichkeit, um die Ausführung von Code-Teilen mit Threads mit einem "Feuer-und-Vergessen" -Ansatz zu erledigen.
Hinweis: Das Folgende ist einfacher zu befolgen, wenn Sie zuerst den Quellcode herunterladen.
Während ich mehr Möglichkeiten erkundete, um einige meiner Funktionen in einem Thread ausführen zu lassen, habe ich mich dazu entschlossen, auch die von Andreas Hausladen entwickelte Unit "AsyncCalls.pas" auszuprobieren. Andys AsyncCalls - Asynchronous Function Calls Unit ist eine weitere Bibliothek, die ein Delphi-Entwickler verwenden kann, um die Implementierung eines Thread-Ansatzes zur Ausführung von Code zu erleichtern.
Aus Andys Blog: Mit AsyncCalls können Sie mehrere Funktionen gleichzeitig ausführen und zu jedem Zeitpunkt in der Funktion oder Methode synchronisieren, mit der sie gestartet wurden. Die AsyncCalls-Einheit bietet eine Vielzahl von Funktionsprototypen zum Aufrufen asynchroner Funktionen. Sie implementiert einen Thread-Pool! Die Installation ist sehr einfach: Verwenden Sie einfach asynchrone Aufrufe von jedem Ihrer Geräte und Sie haben sofortigen Zugriff auf Dinge wie "In einem separaten Thread ausführen, Hauptbenutzeroberfläche synchronisieren, bis zum Abschluss warten"..
Neben der kostenlos nutzbaren (MPL-Lizenz) AsyncCalls veröffentlicht Andy regelmäßig seine eigenen Fixes für die Delphi-IDE, wie "Delphi Speed Up" und "DDevExtensions". Ich bin sicher, Sie haben davon gehört (wenn nicht bereits verwendet)..
Im Wesentlichen geben alle AsyncCall-Funktionen eine IAsyncCall-Schnittstelle zurück, mit der die Funktionen synchronisiert werden können. IAsnycCall macht die folgenden Methoden verfügbar:
//v 2.98 von asynccalls.pas
IAsyncCall = Schnittstelle
// wartet bis die Funktion beendet ist und gibt den Rückgabewert zurück
Funktion Sync: Integer;
// gibt True zurück, wenn die asynchrone Funktion beendet ist
Funktion beendet: Boolean;
// gibt den Rückgabewert der asynchronen Funktion zurück, wenn Finished TRUE ist
Funktion ReturnValue: Integer;
// teilt AsyncCalls mit, dass die zugewiesene Funktion in der aktuellen Bedrohung nicht ausgeführt werden darf
procedure ForceDifferentThread;
Ende;
Hier ist ein Beispielaufruf für eine Methode, die zwei Ganzzahlparameter erwartet (die einen IAsyncCall zurückgeben):
TAsyncCalls.Invoke (AsyncMethod, i, Random (500));
Funktion TAsyncCallsForm.AsyncMethod (taskNr, sleepTime: integer): integer;
Start
Ergebnis: = sleepTime;
Sleep (sleepTime);
TAsyncCalls.VCLInvoke (
Verfahren
Start
Protokoll (Format ('erledigt> nr:% d / Tasks:% d / geschlafen:% d', [tasknr, asyncHelper.TaskCount, sleepTime]));
Ende);
Ende;
Mit TAsyncCalls.VCLInvoke können Sie eine Synchronisierung mit Ihrem Hauptthread (Hauptthread der Anwendung - Benutzeroberfläche der Anwendung) durchführen. VCLInvoke kehrt sofort zurück. Die anonyme Methode wird im Hauptthread ausgeführt. Es gibt auch VCLSync, das zurückgibt, als die anonyme Methode im Hauptthread aufgerufen wurde.
Zurück zu meiner Aufgabe "Dateiscanning": Wenn Sie den asyncalls-Threadpool (in einer for-Schleife) mit einer Reihe von TAsyncCalls.Invoke () -Aufrufen füttern, werden die Aufgaben zum internen Pool hinzugefügt und ausgeführt, "wenn die Zeit gekommen ist" ( wenn zuvor hinzugefügte Anrufe beendet wurden).
Die in asnyccalls definierte AsyncMultiSync-Funktion wartet darauf, dass die asynchronen Aufrufe (und andere Punkte) beendet werden. Es gibt einige überladene Möglichkeiten, AsyncMultiSync aufzurufen, und hier ist die einfachste:
Funktion AsyncMultiSync (const Aufführen: Anordnung von IAsyncCall; WaitAll: Boolean = True; Millisekunden: Kardinal = UNENDLICH): Kardinal;
Wenn "wait all" implementiert werden soll, muss ich ein Array von IAsyncCall ausfüllen und AsyncMultiSync in 61-er Schritten ausführen.
Hier ist ein Teil des TAsyncCallsHelper:
ACHTUNG: Teilcode! (vollständiger Code zum Download verfügbar)
Verwendet AsyncCalls;
Art
TIAsyncCallArray = Anordnung von IAsyncCall;
TIAsyncCallArrays = Anordnung von TIAsyncCallArray;
TAsyncCallsHelper = Klasse
Privat
fTasks: TIAsyncCallArrays;
Eigentum Aufgaben: TIAsyncCallArrays lesen fTasks;
Öffentlichkeit
Verfahren Aufgabe hinzufügen(const call: IAsyncCall);
Verfahren WaitAll;
Ende;
WARNUNG: Teilcode!
Verfahren TAsyncCallsHelper.WaitAll;
var
i: ganze Zahl;
Start
zum i: = High (Aufgaben) bis zu Niedrig (Aufgaben) tun
Start
AsyncCalls.AsyncMultiSync (Aufgaben [i]);
Ende;
Ende;
Auf diese Weise kann ich in Gruppen von 61 (MAXIMUM_ASYNC_WAIT_OBJECTS) "alle warten" - d. H. Auf Arrays von IAsyncCall warten.
Mit dem obigen Code sieht mein Hauptcode zum Füttern des Thread-Pools folgendermaßen aus:
Verfahren TAsyncCallsForm.btnAddTasksClick (Sender: TObject);
const
nrItems = 200;
var
i: ganze Zahl;
Start
asyncHelper.MaxThreads: = 2 * System.CPUCount;
ClearLog ("Starten");
zum i: = 1 zu nrItems tun
Start
asyncHelper.AddTask (TAsyncCalls.Invoke (AsyncMethod, i, Random (500));
Ende;
Log ('all in');
// warte alle
//asyncHelper.WaitAll;
// oder erlauben Sie das Abbrechen aller nicht gestarteten Aktionen durch Klicken auf die Schaltfläche "Alle abbrechen":
während NICHT asyncHelper.AllFinished tun Application.ProcessMessages;
Protokoll ('beendet');
Ende;
Ich möchte auch die Möglichkeit haben, Aufgaben, die sich im Pool befinden, aber auf ihre Ausführung warten, abzubrechen.
Leider bietet AsyncCalls.pas keine einfache Möglichkeit, eine Aufgabe abzubrechen, nachdem sie dem Thread-Pool hinzugefügt wurde. Es gibt weder IAsyncCall.Cancel noch IAsyncCall.DontDoIfNotAlreadyExecuting noch IAsyncCall.NeverMindMe.
Damit dies funktioniert, musste ich die AsyncCalls.pas ändern, indem ich versuchte, sie so wenig wie möglich zu ändern. Wenn Andy eine neue Version veröffentlicht, muss ich nur ein paar Zeilen hinzufügen, damit meine Idee "Aufgabe abbrechen" funktioniert.
Folgendes habe ich getan: Ich habe dem IAsyncCall eine "Prozedur Abbrechen" hinzugefügt. Mit der Cancel-Prozedur wird das Feld "FCancelled" (hinzugefügt) festgelegt, das überprüft wird, wenn der Pool mit der Ausführung der Task beginnt. Ich musste die IAsyncCall.Finished-Prozedur (IAsyncCall.Finished) und die TAsyncCall.InternExecuteAsyncCall-Prozedur (TAsyncCall.InternExecuteAsyncCall) leicht ändern, damit der Anruf nicht ausgeführt wird, wenn er abgebrochen wurde..
Sie können WinMerge verwenden, um Unterschiede zwischen Andys ursprünglichem asynccall.pas und meiner geänderten Version (im Download enthalten) leicht zu lokalisieren..
Sie können den vollständigen Quellcode herunterladen und erkunden.
Das CancelInvocation Methode stoppt den Aufruf von AsyncCall. Wenn der AsyncCall bereits verarbeitet wird, hat ein Aufruf von CancelInvocation keine Auswirkung und die Funktion Canceled gibt False zurück, da der AsyncCall nicht abgebrochen wurde.
Das Abgesagt Methode gibt True zurück, wenn der AsyncCall durch CancelInvocation abgebrochen wurde.
Das Vergessen Methode entfernt die Verknüpfung der IAsyncCall-Schnittstelle mit dem internen AsyncCall. Dies bedeutet, dass der asynchrone Aufruf weiterhin ausgeführt wird, wenn der letzte Verweis auf die IAsyncCall-Schnittstelle nicht mehr vorhanden ist. Die Methoden der Schnittstelle lösen eine Ausnahme aus, wenn sie nach dem Aufruf von Forget aufgerufen werden. Die asynchrone Funktion darf nicht in den Haupt-Thread aufgerufen werden, da sie ausgeführt werden kann, nachdem der TThread.Synchronize / Queue-Mechanismus von der RTL heruntergefahren wurde, was zu einem Dead Lock führen kann.
Beachten Sie jedoch, dass Sie weiterhin von meinem AsyncCallsHelper profitieren können, wenn Sie warten müssen, bis alle asynchronen Aufrufe mit "asyncHelper.WaitAll" abgeschlossen sind. oder wenn du "CancelAll" brauchst.