Exemplu de pool Delphi Thread Pool folosind AsyncCalls

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.

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 - 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).

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 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.

Pool de fire în AsyncCalls

Î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).

Așteptați toate finalizările IAsyncCalls

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.

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;
     
     
     
     
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;

Anulați toate? - Trebuie să schimbați AsyncCalls.pas :(

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.

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ă 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”.