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șierului” secvențială de 500-2000 de fișiere din abordarea non-threaded într-una filetată. 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ă asemănătoare cu coada 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 filetat).
Î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 zillion de modalități 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 executarea cu filet a bucăților de cod.
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 - Unitatea de apeluri funcționale asincrone este o altă bibliotecă pe care un dezvoltator Delphi o poate folosi pentru a ușura durerea implementării unei abordări 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 Speed Up” și „DDevExtensions” sunt sigur că ați auzit de (dacă nu folosiți deja).
Î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 alocată nu trebuie să fie executată în zona actuală
procedura ForceDifferentThread;
Sfârșit;
Iată un exemplu de apel la o metodă care așteaptă doi parametri întregi (returnarea unui IAsyncCall):
TAsyncCalls.Invoke (AsyncMethod, i, Random (500));
funcţie TAsyncCallsForm.AsyncMethod (taskNr, sleepTime: integer): număr întreg;
începe
rezultat: = sleepTime;
Sleep (sleepTime);
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.
Înapoi la sarcina mea de „scanare fișier”: când alimentați (într-o buclă) pool-ul de asynccalls cu o serie de apeluri TAsyncCalls.Invoke (), sarcinile vor fi adăugate în poolul intern și vor fi executate „când va veni timpul” ( când apelurile adăugate anterior s-au terminat).
Funcția AsyncMultiSync definită în asnyccalls așteaptă finalizarea apelurilor async (și a altor mânere). Există câteva moduri supraîncărcate de a apela AsyncMultiSync, iar aici este cea mai simplă:
funcţie AsyncMultiSync (const Listă: o serie de IAsyncCall; WaitAll: Boolean = True; Milisecunde: Cardinal = INFINIT): Cardinal;
Dacă vreau să fie „așteaptă tot” implementat, trebuie să completez un tablou de IAsyncCall și să fac AsyncMultiSync în felii de 61.
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;
AVERTIZARE: cod parțial!
procedură TAsyncCallsHelper.WaitAll;
var
i: număr întreg;
începe
pentru i: = Mare (Sarcini) downto Low (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 * System.CPUCount;
ClearLog ( 'pornire');
pentru i: = 1 la nrItems do
începe
asyncHelper.AddTask (TAsyncCalls.Invoke (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.AllFinished do Application.ProcessMessages;
Jurnal ( 'terminat');
Sfârșit;
Aș dori, de asemenea, o modalitate de „anulare” a acelor sarcini care sunt î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.Cancel sau IAsyncCall.DontDoIfNotAlreadyExecuting sau IAsyncCall.NeverMindMe.
Pentru ca aceasta să funcționeze, a trebuit să schimb AsyncCalls.pas încercând să o modific cât mai puțin posibil - așa că, atunci când Andy lansează o nouă versiune, trebuie să adaug câteva rânduri pentru ca ideea mea „Anulează sarcina” să funcționeze..
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.Finished (astfel încât un raport de apel s-a terminat chiar și atunci când a fost anulat) și procedura TAsyncCall.InternExecuteAsyncCall (să nu execute apelul dacă a fost anulat).
Puteți utiliza WinMerge pentru a localiza cu ușurință diferențele dintre asynccall.pas originală ale lui Andy și versiunea mea modificată (inclusă în descărcare).
Puteți descărca codul sursă complet și puteți explora.
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ă ce mecanismul TThread.Synchronize / Queue a fost oprit de RTL ceea ce poate provoca 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”.