Toto je můj další testovací projekt, abych zjistil, která knihovna podprocesů pro Delphi by mě nejlépe hodila pro můj úkol „skenování souborů“, který bych chtěl zpracovat ve více vláknech / ve fondu vláken.
Opakování mého cíle: transformovat mé sekvenční „skenování souborů“ 500–2 000 + souborů z přístupu bez podprocesů na postup s podprocesy. Neměl bych mít najednou spuštěno 500 vláken, proto bych chtěl použít fond vláken. Fond podprocesů je třída podobná frontě, která dodává dalšímu běhu podprocesů další úlohu z fronty.
První (velmi základní) pokus byl proveden jednoduše rozšířením třídy TThread a implementací metody Execute (můj analyzátor řetězců se závity).
Vzhledem k tomu, že společnost Delphi nemá implementovanou třídu podprocesů po vybalení z krabice, ve svém druhém pokusu jsem se pokusila použít OmniThreadLibrary od Primoz Gabrijelcic.
OTL je fantastický, má miliony způsobů, jak spustit úkol na pozadí, způsob, jak jít, pokud chcete mít přístup "oheň a zapomenout" k předávání závitových provedení kusů vašeho kódu.
AsyncCalls Andreas Hausladen
Poznámka: Následující kroky by bylo jednodušší sledovat, pokud si nejprve stáhnete zdrojový kód.
Při zkoumání více způsobů, jak nechat některé z mých funkcí provádět v podprocesu, jsem se také rozhodl vyzkoušet jednotku „AsyncCalls.pas“ vyvinutou Andreasem Hausladenem. Andy je AsyncCalls - Asynchronní volání funkcí jednotka je další knihovna, kterou může vývojář Delphi použít ke zmírnění bolesti implementace vláknového přístupu k provedení nějakého kódu.
Z blogu Andyho: S AsyncCalls můžete provádět více funkcí současně a synchronizovat je v každém bodě funkce nebo metody, která je spustila... Jednotka AsyncCalls nabízí celou řadu prototypů funkcí pro volání asynchronních funkcí... Implementuje fond vláken! Instalace je velmi snadná: stačí použít asynchronní volání z kterékoli z vašich jednotek a máte okamžitý přístup k věcem, jako je „spuštění v samostatném vlákně, synchronizace hlavního uživatelského rozhraní, počkejte, dokud nedokončíte“.
Kromě bezplatného použití (licence MPL) AsyncCalls, Andy také často publikuje své vlastní opravy pro Delphi IDE jako „Delphi urychlit" a "DDevExtensions"Jsem si jistý, že jste už slyšeli (pokud již nepoužíváte)."
AsyncCalls in Action
V podstatě všechny funkce AsyncCall vracejí rozhraní IAsyncCall, které umožňuje synchronizovat funkce. IAsnycCall vystavuje následující metody:
//v 2,98 z asynccalls.pas
IAsyncCall = rozhraní
// čeká na dokončení funkce a vrátí vrácenou hodnotu
funkce Sync: Integer;
// vrátí True po dokončení asynchronní funkce
funkce Dokončeno: Boolean;
// vrátí návratovou hodnotu asynchronní funkce, když je Dokončeno TRUE
funkce ReturnValue: Integer;
// řekne AsyncCalls, že přiřazená funkce nesmí být spuštěna v aktuálním vláknu
procedura ForceDifferentThread;
konec;
Zde je příklad volání metody očekávající dva celočíselné parametry (vrácení IAsyncCall):
TAsyncCalls. Vyvolat (AsyncMethod, i, Random (500));
funkce TAsyncCallsForm. AsyncMethod (taskNr, sleepTime: integer): integer;
začít
result: = sleepTime;
Spánek (sleepTime);
TAsyncCalls. VCLInvoke (
postup
začít
Log (Format ('done> nr:% d / task:% d / sleept:% d', [tasknr, asyncHelper). TaskCount, sleepTime]));
konec);
konec;
TAsyncCalls. VCLInvoke je způsob, jak provést synchronizaci s hlavním vláknem (hlavní vlákno aplikace - uživatelské rozhraní aplikace). VCLInvoke se vrací okamžitě. Anonymní metoda bude spuštěna v hlavním vlákně. K dispozici je také VCLSync, který se vrací, když byla v hlavním vláknu vyvolána anonymní metoda.
Podproces vlákno v AsyncCalls
Zpět k mé úloze „skenování souborů“: při podávání (ve smyčce for)) fondu vláken asynchronního volání s řadou TAsyncCalls. Volání Invoke (), budou úkoly přidány do interního fondu a budou provedeny „když přijde čas“ (po dokončení dříve přidaných hovorů).
Počkejte na dokončení všech IAsyncCalls
Funkce AsyncMultiSync definovaná v asnyccalls čeká na ukončení asynchronních volání (a dalších popisovačů). Je tu pár přetížený způsoby, jak volat AsyncMultiSync, a tady je ten nejjednodušší:
funkce AsyncMultiSync (konst Seznam: řada IAsyncCall; WaitAll: Boolean = True; Milisekundy: kardinál = INFINITE): kardinál;
Pokud chci mít implementaci „počkat na vše“, musím vyplnit pole IAsyncCall a udělat AsyncMultiSync v plátcích 61.
Můj pomocník AsnycCalls
Tady je kousek TAsyncCallsHelper:
VAROVÁNÍ: částečný kód! (celý kód je k dispozici ke stažení)
použití AsyncCalls;
typ
TIAsyncCallArray = řada IAsyncCall;
TIAsyncCallArrays = řada TIAsyncCallArray;
TAsyncCallsHelper = třída
soukromé
fTasks: TIAsyncCallArrays;
vlastnictví Úkoly: TIAsyncCallArrays číst fTasks;
veřejnost
postup AddTask (konst volejte: IAsyncCall);
postup WaitAll;
konec;
VAROVÁNÍ: částečný kód!
postup TAsyncCallsHelper. WaitAll;
var
i: celé číslo;
začít
pro i: = Vysoká (Úkoly) dolů Nízké (Úkoly) dělat
začít
AsyncCalls. AsyncMultiSync (Úkoly [i]);
konec;
konec;
Tímto způsobem mohu „čekat vše“ v kusy 61 (MAXIMUM_ASYNC_WAIT_OBJECTS) - tj. Čekat na pole IAsyncCall.
S výše uvedeným můj hlavní kód pro krmení fondu vláken vypadá takto:
postup TAsyncCallsForm.btnAddTasksClick (Odesílatel: TObject);
konst
nrItems = 200;
var
i: celé číslo;
začít
asyncHelper. MaxThreads: = 2 * Systém. CPUCount;
ClearLog ('start');
pro i: = 1 k číslům dělat
začít
asyncHelper. AddTask (TAsyncCalls. Vyvolat (AsyncMethod, i, Random (500)));
konec;
Log ('vše v');
// počkej všechny
//asyncHelper.WaitAll;
// nebo povolit zrušení všeho, co nebylo zahájeno kliknutím na tlačítko „Zrušit vše“:
zatímco NE asyncHelper. Vše hotovo dělat Aplikace. ProcessMessages;
Protokol ('hotový');
konec;
Zrušit vše? - Musí změnit AsyncCalls.pas :(
Také bych chtěl mít způsob, jak „zrušit“ ty úkoly, které jsou v bazénu, ale čekají na jejich provedení.
Bohužel AsyncCalls.pas neposkytuje jednoduchý způsob zrušení úlohy, jakmile bude přidána do fondu podprocesů. Neexistuje žádný IAsyncCall. Zrušte nebo IAsyncCall. DontDoIfNotAlreadyExecuting nebo IAsyncCall. NeverMindMe.
Aby to fungovalo, musel jsem změnit AsyncCalls.pas tím, že jsem se pokusil změnit to co nejméně - tak že když Andy vydá novou verzi, musím přidat jen několik řádků, abych měl nápad „Zrušit úkol“ pracovní.
Tady je to, co jsem udělal: Přidal jsem do procedury IAsyncCall „zrušení procedury“. Procedura Storno nastaví pole „FCancelled“ (přidáno), které se zkontroluje, když se fond chystá začít provádět úlohu. Potřeboval jsem lehce změnit IAsyncCall. Dokončeno (aby zprávy o hovorech skončily i po zrušení) a TAsyncCall. Postup InternExecuteAsyncCall (neprovádět hovor, pokud byl zrušen).
Můžeš použít WinMerge snadno najít rozdíly mezi Andyho původní asynccall.pas a mojí změnou verzí (součástí stahování).
Můžete si stáhnout celý zdrojový kód a prozkoumat.
Zpověď
OZNÁMENÍ! :)
CancelInvocation metoda zabraňuje vyvolání AsyncCall. Pokud je AsyncCall již zpracován, volání na CancelInvocation nemá žádný účinek a zrušená funkce vrátí False, protože AsyncCall nebyl zrušen.
Zrušeno metoda vrátí True, pokud byl AsyncCall zrušen CancelInvocation.
Zapomenout metoda odpojí rozhraní IAsyncCall od interního AsyncCall. To znamená, že pokud je poslední odkaz na rozhraní IAsyncCall pryč, bude asynchronní volání stále provedeno. Metody rozhraní vyvolají výjimku, pokud jsou zavolány po volání Forget. Async funkce nesmí zavolat do hlavního vlákna, protože by mohla být provedena po TThread. Mechanismus synchronizace / fronty byl vypnut RTL, což může způsobit mrtvý zámek.
Mějte však na paměti, že stále můžete těžit z mého AsyncCallsHelper, pokud potřebujete počkat, až všechna asynchronní volání skončí „asyncHelper. WaitAll "; nebo pokud potřebujete „Zrušit vše“.