Acesta este următorul meu proiect de testare pentru a vedea ce bibliotecă de filetare pentru Delphi m-ar fi cea mai bună pentru sarcina mea de „scanare fișiere” pe care aș dori să o procesez în mai multe fire / într-un pool de fire.
Pentru a-mi repeta obiectivul: transformați „scanarea” fișieră secvențială de 500-2000 de fișiere din abordarea non-threaded într-una cu filetare. Nu ar trebui să am 500 de fire care rulează simultan, aș dori să folosesc un pool de fire. Un pool de thread este o clasă de tip coadă care alimentează un număr de fire care rulează cu următoarea sarcină din coadă.
Prima încercare (foarte de bază) a fost făcută prin simpla extindere a clasei TThread și punerea în aplicare a metodei Execute (parserul meu cu șir de filet).
Întrucât Delphi nu are o clasă de pool de fire implementată din cutie, în a doua mea încercare am încercat să folosesc OmniThreadLibrary de Primoz Gabrijelcic.
OTL este fantastic, are moduri zillion de a rula o sarcină într-un fundal, un mod de a merge dacă doriți să aveți o abordare "fire-and-uită" pentru a înmâna execuția cu filet a bucăților de cod.
AsyncCalls de Andreas Hausladen
Notă: ceea ce urmează ar fi mai ușor de urmărit dacă descărcați mai întâi codul sursă.
În timp ce explorez mai multe modalități de a executa unele dintre funcțiile mele într-o manieră filetată, am decis să încerc și unitatea „AsyncCalls.pas” dezvoltată de Andreas Hausladen. Andy AsyncCalls - Apelarea funcției asincrone unitate este o altă bibliotecă pe care un dezvoltator Delphi o poate folosi pentru a ușura durerea punerii în aplicare a abordării filetate la executarea unor coduri.
De pe blogul lui Andy: Cu AsyncCalls puteți executa mai multe funcții în același timp și le puteți sincroniza la fiecare punct al funcției sau metodei care le-a pornit... Unitatea AsyncCalls oferă o varietate de prototipuri de funcții pentru a apela funcții asincrone... Implementează un pool de fire! Instalarea este super ușoară: folosiți asynccalls din oricare dintre unitățile dvs. și aveți acces instantaneu la lucruri precum „executați într-un thread separat, sincronizați UI-ul principal, așteptați până la final”.
Pe lângă AsyncCalls (licență MPL) gratuită de utilizat, Andy publică frecvent propriile corecții pentru Delphi IDE precum "Delphi accelerează" și "DDevExtensions„Sunt sigur că ați auzit (dacă nu folosiți deja).
AsyncCalls In Action
În esență, toate funcțiile AsyncCall returnează o interfață IAsyncCall care permite sincronizarea funcțiilor. IAsnycCall expune următoarele metode:
//v 2.98 din asynccalls.pas
IAsyncCall = interfață
// așteaptă până când funcția este terminată și returnează valoarea returnată
funcție Sincronizare: Integer;
// returnează True atunci când funcția asincron este terminată
funcție Finisat: Boolean;
// returnează valoarea de întoarcere a funcției asincrone, atunci când Finished este TRUE
funcție ReturnValue: Integer;
// spune AsyncCalls că funcția atribuită nu trebuie să fie executată în acea perioadă
procedura ForceDifferentThread;
Sfârșit;
Iată un exemplu de apel către o metodă care așteaptă doi parametri întregi (returnarea unui IAsyncCall):
TAsyncCalls. Invoca (AsyncMethod, i, Random (500));
funcţie TAsyncCallsForm. AsyncMethod (taskNr, sleepTime: integer): număr întreg;
începe
rezultat: = sleepTime;
Somn (somn);
TAsyncCalls. VCLInvoke (
procedură
începe
Jurnal (Format ('făcut> nr:% d / task:% d / slept:% d', [tasknr, asyncHelper). TaskCount, sleepTime]));
Sfârșit);
Sfârșit;
TAsyncCalls. VCLInvoke este o modalitate de a face sincronizarea cu firul principal (firul principal al aplicației - interfața de utilizator a aplicației). VCLInvoke revine imediat. Metoda anonimă va fi executată în firul principal. Există, de asemenea, VCLSync care se întoarce când a fost apelată metoda anonimă în firul principal.
Pool de fire în AsyncCalls
Înapoi la sarcina mea de „scanare fișier”: când alimentați (într-o buclă pentru), grupul de fire asynccalls cu o serie de TAsyncCalls. Invocați () apeluri, sarcinile vor fi adăugate la pool-ul intern și vor fi executate „când va veni timpul” (când apelurile adăugate anterior au terminat).
Așteptați toate finalizările IAsyncCalls
Funcția AsyncMultiSync definită în asnyccalls așteaptă finalizarea apelurilor async (și a altor mânere). Sunt cateva supraîncărcat modalități de a apela AsyncMultiSync, și aici este cel mai simplu:
funcţie AsyncMultiSync (const Listă: o serie de IAsyncCall; WaitAll: Boolean = True; Milisecunde: Cardinal = INFINIT): Cardinal;
Dacă vreau să fiu implementat „așteptați tot”, trebuie să completez un tablou de IAsyncCall și să fac AsyncMultiSync în felii de 61.
AsnycCalls My Helper
Iată o piesă din TAsyncCallsHelper:
ATENȚIE: cod parțial! (cod complet disponibil pentru descărcare)
utilizări AsyncCalls;
tip
TIAsyncCallArray = o serie de IAsyncCall;
TIAsyncCallArrays = o serie de TIAsyncCallArray;
TAsyncCallsHelper = clasă
privat
fTasks: TIAsyncCallArrays;
proprietate Sarcini: TIAsyncCallArrays citit fTasks;
public
procedură AddTask (const apel: IAsyncCall);
procedură WaitAll;
Sfârșit;
ATENȚIE: cod parțial!
procedură TAsyncCallsHelper. WaitAll;
var
i: număr întreg;
începe
pentru i: = Mare (Sarcini) downto Scăzut (sarcini) do
începe
AsyncCalls. AsyncMultiSync (Sarcini [i]);
Sfârșit;
Sfârșit;
În acest fel, pot „aștepta totul” în bucăți de 61 (MAXIMUM_ASYNC_WAIT_OBJECTS) - adică în așteptarea tablourilor de IAsyncCall.
Cu cele de mai sus, codul meu principal pentru alimentarea setului de fire arată:
procedură TAsyncCallsForm.btnAddTasksClick (Expeditor: TObject);
const
nrItems = 200;
var
i: număr întreg;
începe
asyncHelper. MaxThreads: = 2 * Sistem. CPUCount;
ClearLog ( 'pornire');
pentru i: = 1 la nrItems do
începe
asyncHelper. AddTask (TAsyncCalls. Invoca (AsyncMethod, i, Random (500)));
Sfârșit;
Jurnal ('all in');
// așteptați toate
//asyncHelper.WaitAll;
// sau permiteți anularea tuturor celor care nu au început, făcând clic pe butonul "Anulează tot"
în timp ce NU asyncHelper. Totul gata do Aplicație. ProcessMessages;
Jurnal ( 'terminat');
Sfârșit;
Anulați toate? - Trebuie să schimbați AsyncCalls.pas :(
Aș dori, de asemenea, o modalitate de „anulare” a acelor sarcini care se află în bazin, dar care așteaptă executarea lor.
Din păcate, AsyncCalls.pas nu oferă o modalitate simplă de a anula o sarcină odată ce a fost adăugată la pool-ul thread. Nu există IAsyncCall. Anulați sau IAsyncCall. DontDoIfNotAlreadyExecuting sau IAsyncCall. Nu mă lua în seamă.
Pentru ca aceasta să funcționeze, a trebuit să schimb AsyncCalls.pas încercând să îl modific cât mai puțin posibil - deci că atunci când Andy lansează o nouă versiune trebuie să adaug doar câteva rânduri pentru a avea ideea mea „Anulează sarcina” lucru.
Iată ce am făcut: am adăugat o „procedură de anulare” la IAsyncCall. Procedura Anulare stabilește câmpul „FCancelat” (adăugat) care este verificat atunci când grupul este pe punctul de a începe executarea sarcinii. A trebuit să modific ușor IAsyncCall. Finalizat (astfel încât rapoartele unui apel s-au terminat chiar și atunci când a fost anulat) și TAsyncCall. Procedura InternExecuteAsyncCall (pentru a nu executa apelul dacă a fost anulată).
Poți să folosești WinMerge pentru a localiza cu ușurință diferențele dintre asynccall.pas original și Andy versiunea modificată (inclus în descărcare).
Puteți descărca codul sursă complet și puteți explora.
Mărturisire
ÎNȘTIINȚARE! :)
CancelInvocation metoda oprește AsyncCall să fie invocat. Dacă AsyncCall este deja procesat, un apel către CancelInvocation nu are efect și funcția Anulată va returna False, deoarece AsyncCall nu a fost anulată.
Anulat metoda returnează True dacă AsyncCall a fost anulată de CancelInvocation.
A uita metoda deconectează interfața IAsyncCall de la AsyncCall intern. Aceasta înseamnă că, dacă ultima trimitere la interfața IAsyncCall a dispărut, apelul asincron va fi încă executat. Metodele interfeței vor arunca o excepție dacă sunt apelate după apelarea Forget. Funcția async nu trebuie să apeleze la firul principal, deoarece ar putea fi executată după TThread. Mecanismul de sincronizare / coadă a fost oprit de RTL ceea ce poate cauza o blocare moartă.
Rețineți, totuși, că mai puteți beneficia de AsyncCallsHelper, dacă aveți nevoie să așteptați ca toate apelurile async să termine cu „asyncHelper. WaitAll "; sau dacă aveți nevoie de „CancelAll”.