Inapoi la toate articolele

Are lag, nu are lag, mă iubește, nu mă iubește...nu e ok. Programarea nu este loterie!

Veniți la interviu. “Am învățat programare de UI cu swing/swt/jface”  îmi spuneți. 

Minunat! Și vine și prima intrebare, fundamentală - Ce este EDT si cum trebuie folosit?

Din păcate, de cele mai multe ori, răspunsul este doar o încruntare de sprâncene...despre ce vorbește omul ăsta? și atunci întreb –“ îți place ca telefonul tău mobil să aibă lag?” acum întrebarea e clară. Un zâmbet larg îmi răspunde "nu, si nici nu are cum. Telefonul meu e quad core..."

Mă întristez și mai tare. Confuzia este totală.

Când vorbim despre interfața grafică, vorbim despre interacțiune, despre comunicare între mine utilizator, fericit atunci când pot să tastez sau să dau un click în voie și device-ul a cărui grafică funcționează fluid, impecabil. Pot să arăt pozele favorite și filmulețe făcute în vacanță. Și sunt mândru că totul merge perfect și la rezoluție HD... dacă nu, familia/prietenii îmi zâmbesc condescendent... tehnologia devine o sursă de stres și atât.

Așadar, ca să evităm "blocajele" să raspundem la întrebarea: cine desenează ecranul și cine se ocupă de tratarea evenimentelor de click/tastatură?

Da, exact, este el, singurul și unicul - Event Dispatch Thread (denumirea folosită în Java sau UI thread în SWT, .NET Framework și Android). Și da, există și UN event queue unde se acumulează TOATE evenimentele care trebuie tratate. Evenimentele din coadă sunt procesate/executate în ordine. Fiecare eveniment înseamnă timp de procesare - ceva de făcut. Ideal este ca fiecare eveniment să fie tratat cu maxim de viteză/minim de timp, pt că este executat pe EDT. Altfel.... ai lag. Sau mai rău - totul îngheață... și parcă aud: Java este de vină, a fost o alegere proastă, am auzit eu că se mișcă greu... sau, mai trebuie hardware, nu ai destule core-uri , nu ai memorie...

Mda, de obicei problema asta nu are nicio treabă cu  "argumentele" de mai sus deși le auzim atât de des - problema are însă legătură cu faptul că tu, programatorule, ai folosit aiurea EDT-ul. Faci operații costisitoare de lungă durată pe EDT.

"Păi ce eu am ales să îl folosesc?" - mă întrebi supărat. Eu nu am cerut nicăieri în aplicație să folosesc acest.... EDT. Eu cred că e de la Java... sau de la hardware.

 

Îți spun că nu e nicio problemă cu hardware-ul tău, ai destul. La fel nu e o problemă cu Java, puteai să folosești C sau C++, era la fel. De ce? Pentru că EDT-ul este abordarea comună din mai toate librăriile de UI. Indiferent de sistem, Windows, Linux, Android, iOS...

Au fost și mai sunt încercări de librării grafice thread safe dar în final mai toate folosesc până la urmă un singur thread de UI și event dispatch. Deci nu ai cum să îl eviți și fiind un singur thread nu are treabă cu numărul de procesoare sau core-uri pe care ai dat tu o grămadă de bani uitându-te la reclamele tv...

 

Repetă acestă mantră:

"Access to the GUI is serialized and other threads may submit some code to be executed in the EDT through a EDT message queue." 

 

Deci regula de aur este următoarea: orice altceva în afară de operațiile pe UI și prinderea evenimentelor (click sau key) trebuie procesate pe un alt thread. Un worker thread. Deci, prinzi evenimentul, iei rapid o decizie și trimiți munca altui thread. Exact, ca un șef eficient :) Când e gata, trimiți rezultatul în queue pentru EDT. Acum, că ai un singur worker thread, că ai mai multe, că folosești un pool de threads, că le distribui pe un anumit procesor/core asta este o altă discuție și tine de optimizări. Noi vorbim acum de chestiuni fundamentale, simple și obligatorii - fără ele faceți programare la nimereală. Sună dur dar mai tare deranjează aplicațiile prost scrise care sugerează indirect că limbajele/tehnologiile sunt proaste...

 

Așadar, am o metodă - un listener, care este apelat atunci când eu fac un click sau apăs o tastă - și care se execută automat și fără intervenția ta specială, pe ED thread. Deci, în listener sunt pe EDT.

Poți să verifici simplu - System.out.print(Thread.currentTread());. Bun, e bine că ai verificat. Nu trebuie să mă crezi pe cuvânt. Să verificați și să testați, este o practică obligatorie în muncă  de programare, nu un opțional!

 

Uite un scenariu: să ne imaginăm că butonul pornește un download, un fișier mai mare, un film de exemplu ca să înțelegem mai bine impactul. dacă eu în listener, adică pe EDT, fac această operație care poate să dureze minute bune, aplicația va sta înghețată, tot acest timp. UI înghețat, totul blocat, asta pentru că folosești EDT pentru download.

Dacă însă pornești un alt thread din listener, creat și controlat de tine, care să preia acestă muncă, alfel se va vedea viața. Adică un progress bar frumos, cu buton de stop eventual (atenție că aici mai puteți face o greșeală - nu folosiți un dialog modal, adică blocant, unde afișați progress bar-ul) și o aplicație care în continuare răspunde impecabil. Și, de ce nu, mai pot porni încă un download dacă vreau, să descarc două filme în paralel. "Face sens", nu? 

 

Bun, presupunem că acum implementarea e corectă, folosim un worker thread pentru download, dar îmi veți spune că nu merge progress bar-ul cum trebuie, că face update o singură dată direct pe 100% când e gata adus fișierul. Nu e ok, nu e cool...

 

Păi da, pentru că aici avem iar o regulă care trebuie să fie clară şi bătută în cuie. NU pot să afișez/desenez/apelez/folosesc componentele Swing (sau orice altă librărie grafică) din orice thread, ci NUMAI din EDT. 

Cum facem să apelăm din când în când EDT pentru a face update pe progress bar sau cum verificăm ca butonul de stop (în caz că îl avem definit) a fost apăsat și utilizatorul vrea să oprească procesul/threadul de download în următorul articol. Și da, cu imagini și cod :)

Autor: Teo Velicu, Software Architect