Computerwissenschaften

Beispiel für einen Delphi-Thread-Pool mit AsyncCalls

Dies ist mein nächstes Testprojekt, um herauszufinden, welche Threading-Bibliothek für Delphi für meine Aufgabe „Dateiscannen“, die ich in mehreren Threads / in einem Thread-Pool verarbeiten möchte, am besten geeignet ist.

Um mein Ziel zu wiederholen: Verwandeln Sie mein sequentielles „Datei-Scannen“ von mehr als 500-2000 Dateien vom Nicht-Thread-Ansatz in einen Thread-Ansatz. Ich sollte nicht 500 Threads gleichzeitig laufen lassen, daher möchte ich einen Thread-Pool verwenden. Ein Thread-Pool ist eine warteschlangenähnliche Klasse, die eine Reihe laufender Threads mit der nächsten Aufgabe aus der Warteschlange versorgt.

Der erste (sehr grundlegende) Versuch wurde unternommen, indem einfach die TThread-Klasse erweitert und die Execute-Methode (mein Thread-String-Parser) implementiert wurde.

Da in Delphi keine sofort einsatzbereite Thread-Pool-Klasse implementiert ist, habe ich in meinem zweiten Versuch versucht, OmniThreadLibrary von Primoz Gabrijelcic zu verwenden.

OTL ist fantastisch und bietet unzählige Möglichkeiten, eine Aufgabe im Hintergrund auszuführen. Dies ist ein guter Weg, wenn Sie einen „Feuer-und-Vergessen“ -Ansatz für die Ausführung von Codeteilen mit Threads wünschen.

 

AsyncCalls von Andreas Hausladen

Hinweis: Das Folgende ist einfacher zu befolgen, wenn Sie zuerst den Quellcode herunterladen.

Während ich nach weiteren Möglichkeiten gesucht habe, einige meiner Funktionen in einem Thread auszuführen, habe ich mich entschlossen, auch die von Andreas Hausladen entwickelte Einheit „AsyncCalls.pas“ auszuprobieren. Andys AsyncCalls – Asynchrone Funktionsaufrufeinheit ist eine weitere Bibliothek, die ein Delphi-Entwickler verwenden kann, um die Implementierung eines Thread-Ansatzes für die Ausführung von Code zu vereinfachen.

Aus Andys Blog: Mit AsyncCalls können Sie mehrere Funktionen gleichzeitig ausführen und an jedem Punkt der Funktion oder Methode, mit der sie gestartet wurden, synchronisieren. … Die AsyncCalls-Einheit bietet eine Vielzahl von Funktionsprototypen zum Aufrufen asynchroner Funktionen. … Es implementiert einen Thread-Pool! Die Installation ist sehr einfach: Verwenden Sie einfach Asynccalls von einem Ihrer Geräte und Sie haben sofort Zugriff auf Dinge wie „In einem separaten Thread ausführen, Hauptbenutzeroberfläche synchronisieren, warten, bis der Vorgang abgeschlossen ist“.

Neben den kostenlos zu verwendenden (MPL-Lizenz) AsyncCalls veröffentlicht Andy häufig auch seine eigenen Fixes für die Delphi-IDE wie “ Delphi Speed ​​Up. und “ DDevExtensions „, von denen Sie sicher schon gehört haben (falls Sie sie noch nicht verwenden).

 

AsyncCalls in Aktion

Im Wesentlichen geben alle AsyncCall-Funktionen eine IAsyncCall-Schnittstelle zurück, mit der die Funktionen synchronisiert werden können. IAsnycCall stellt die folgenden Methoden zur Verfügung:










// v 2.98 von asynccalls.pas 
 IAsyncCall=interface 
 // wartet bis die Funktion beendet ist und gibt die Rückgabewertfunktion zurück 
 Sync: Integer; 
 // Gibt True zurück, wenn die Asynchronfunktion beendet ist. 
 Funktion Fertig: Boolean; 
 // gibt den Rückgabewert der Asynchronfunktion zurück, wenn Finished die TRUE- 
 Funktion ist ReturnValue: Integer; 
 // teilt AsyncCalls mit, dass die zugewiesene Funktion in der aktuellen Bedrohungsprozedur nicht ausgeführt werden darf 
 ForceDifferentThread; 
Ende;

Hier ist ein Beispielaufruf für eine Methode, die zwei ganzzahlige Parameter erwartet (Rückgabe eines IAsyncCall):

 










 TAsyncCalls.Invoke (AsyncMethod, i, Random (500));










Funktion TAsyncCallsForm.AsyncMethod (taskNr, sleepTime: integer): integer; Ergebnis 
beginnen
 :=sleepTime;

Schlaf (sleepTime);

TAsyncCalls.VCLInvoke (
Prozedur
begin
Log (Format (‚erledigt> nr:% d / Aufgaben:% d / Schlaf:% d‘, [tasknr, asyncHelper.TaskCount, sleepTime]));
end );
Ende ;

Mit TAsyncCalls.VCLInvoke können Sie eine Synchronisierung mit Ihrem Hauptthread (dem Hauptthread der Anwendung – der Benutzeroberfläche Ihrer Anwendung) durchführen. VCLInvoke kehrt sofort zurück. Die anonyme Methode wird im Hauptthread ausgeführt. Es gibt auch VCLSync, das zurückgibt, wenn die anonyme Methode im Hauptthread aufgerufen wurde.

 

Thread-Pool in AsyncCalls

Zurück zu meiner Aufgabe „Scannen von Dateien“: Wenn der asynchrone Thread-Pool (in einer for-Schleife) mit einer Reihe von TAsyncCalls.Invoke () -Aufrufen gespeist wird, werden die Aufgaben dem internen Pool hinzugefügt und „zu gegebener Zeit“ ausgeführt ( wenn zuvor hinzugefügte Anrufe beendet wurden).

 

Warten Sie, bis alle IAsyncCalls abgeschlossen sind

Die in asnyccalls definierte AsyncMultiSync-Funktion wartet auf den Abschluss der asynchronen Aufrufe (und anderer Handles). Es gibt einige überladene Möglichkeiten, AsyncMultiSync aufzurufen, und hier ist die einfachste:

 










Funktion AsyncMultiSync ( const List: Array von IAsyncCall; WaitAll: Boolean=True; Millisekunden: Cardinal=INFINITE): Cardinal;

Wenn ich „wait all“ implementieren möchte, muss ich ein Array von IAsyncCall ausfüllen und AsyncMultiSync in Slices von 61 ausführen.

 

Mein AsnycCalls-Helfer

Hier ist ein Teil des TAsyncCallsHelper:










WARNUNG: Teilcode! (vollständiger Code zum Herunterladen verfügbar) 
verwendet AsyncCalls;

Typ
TIAsyncCallArray=Array von IAsyncCall;
TIAsyncCallArrays=Array von TIAsyncCallArray;

TAsyncCallsHelper=Klasse
private
fTasks: TIAsyncCallArrays;
Eigenschaft Aufgaben: TIAsyncCallArrays read fTasks;
öffentliche
Prozedur AddTask ( const call: IAsyncCall);
Prozedur WaitAll;
Ende ;











WARNUNG: Teilcode! 
Prozedur TAsyncCallsHelper.WaitAll; 
var
 i: Ganzzahl; 
beginnt 
 für i:=High (Aufgaben) downto Low (Aufgaben) Sie 
 beginnen
 AsyncCalls.AsyncMultiSync (Aufgaben [i]); 
 Ende ; 
Ende ;

Auf diese Weise kann ich in Blöcken von 61 (MAXIMUM_ASYNC_WAIT_OBJECTS) „alle warten“ – dh auf Arrays von IAsyncCall warten.

Mit dem oben Gesagten sieht mein Hauptcode zum Zuführen des Thread-Pools folgendermaßen aus:

 










Prozedur TAsyncCallsForm.btnAddTasksClick (Absender: TObject); 
const
 nrItems=200; 
var
 i: Ganzzahl; 
begin
 asyncHelper.MaxThreads:=2 * System.CPUCount;

ClearLog (‚Start‘);

für i:=1 bis nrItems Sie
beginnen
asyncHelper.AddTask (TAsyncCalls.Invoke (AsyncMethod, i, Random (500)));
Ende ;

Log (‚all in‘);

// warte alle
//asyncHelper.WaitAll;

// oder erlauben Sie das Abbrechen aller nicht gestarteten durch Klicken auf die Schaltfläche „Alle abbrechen“:
während NICHT asyncHelper.AllFinished do Application.ProcessMessages;

Log (‚fertig‘);
Ende ;

 

Alle Absagen? – Die AsyncCalls.pas müssen geändert werden 🙁

Ich hätte auch gerne eine Möglichkeit, die Aufgaben, die sich im Pool befinden, aber auf ihre Ausführung warten, „abzubrechen“.

Leider bietet AsyncCalls.pas keine einfache Möglichkeit, eine Aufgabe abzubrechen, sobald sie dem Thread-Pool hinzugefügt wurde. Es gibt kein IAsyncCall.Cancel oder IAsyncCall.DontDoIfNotAlreadyExecuting oder 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. Die Prozedur Abbrechen legt das Feld „FCancelled“ (hinzugefügt) fest, das überprüft wird, wenn der Pool mit der Ausführung der Aufgabe beginnen soll. Ich musste IAsyncCall.Finished (damit ein Anruf auch dann beendet wird, wenn er abgebrochen wurde) und die TAsyncCall.InternExecuteAsyncCall-Prozedur geringfügig ändern (um den Anruf nicht auszuführen, wenn er abgebrochen wurde).

Sie können WinMerge verwenden. um Unterschiede zwischen Andys ursprünglicher asynccall.pas und meiner geänderten Version (im Download enthalten) leicht zu finden.

Sie können den vollständigen Quellcode herunterladen und erkunden.

 

Bekenntnis

 

BEACHTEN! 🙂 🙂











Die CancelInvocation- Methode verhindert, dass AsyncCall aufgerufen wird. Wenn der AsyncCall bereits verarbeitet wird, hat ein Aufruf von CancelInvocation keine Auswirkung und die Funktion Cancelled gibt False zurück, da der AsyncCall nicht abgebrochen wurde.

Die Cancelled- Methode gibt True zurück, wenn der AsyncCall von CancelInvocation abgebrochen wurde.

Die Forget- Methode hebt die Verknüpfung der IAsyncCall-Schnittstelle mit dem internen AsyncCall auf. 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 den Hauptthread nicht aufrufen, da sie ausgeführt werden kann, nachdem der TThread.Synchronize / Queue-Mechanismus von der RTL heruntergefahren wurde, was zu einer 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 Sie „CancelAll“ benötigen.

Similar Posts

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.