Sunteți pe pagina 1din 2

Programarea în Windows

Home / My courses / PWINDOWS / Capitolul 1. Concepte fundamentale. Partea 2 / Gestiunea evenimentelor și programele secvențiale

Gestiunea evenimentelor și programele secvențiale

Vom complica un pic problema. Fie că utilizatorul vrea ca aplicația noastră să controleze un LED, adică să-l aprindă pentru câteva fracțiuni de secundă, apoi să-l
stingă, folosind pentru aceasta tastatura. Vom folosi în acest scop un fragment de cod clasic pentru Sistemele de operare în timp real – RTOS: cod secvențial,
care face ca un LED roșu de pe placa TivaC LaunchPad să clipească. Ceva de tipul:

              BSP_ledRedOn();

              QXThread_delay(BSP_TIKS_PER_SEC / 3U);

              BSP_ledRedOff();

Vom introduce în WndProc, ceva analogic, care să oblige LED-ul să clipească la apăsarea oricărei taste de pe tastatură. Locul de introducere a acestui fragment
de cod este acolo unde sunt tratate mesajele de la tastatură, adică CASE WM_KEYDOWN. Cu alte cuvinte, va trebui să modificăm acest fragment de cod:

        case WM_KEYDOWN: { // keyboard key has been pressed

            ++wm_keydown_ctr;

            InvalidateRect(me, NULL, FALSE); // force re-paining of the window

            status = WIN_HANDLED; // report event handled

            break;

    }

O implementare tradițională, secvențială, ar fi să pornim LED-ul, după care să așteptăm, adică să blocăm, pentru, să zicem 200 de milisecunde, pentru ca să
vedem culoarea roșie și apoi să-l stingem.

API-ul Windows oferă un echivalent al serviciului RTOS de întârziere, pe nume Sleep(), care blochează LED-ul în roșu și așteaptă  expirarea numărului specificat
de milisecunde.

Deoarece nu putem utiliza un LED pe calculatorul nostru, vom afișa starea LED-ului ca text în centrul zonei client a ferestrei.

Ca și în toate celelalte cazuri, după schimbarea textului de afișat, trebuie să invalidăm dreptunghiul zonei client a ferestrei pentru a forța actualizarea ei.

"Dormim" timp de 200 de milisecunde. După întârzierea introdusă de Sleep(), schimbăm textul LED-ului din RED în „OFF” și invalidăm din nou dreptunghiul
zonei client a ferestrei.

Ca și celelalte variabile de stare, indicatorul de text LED trebuie definit fiind static în Sleep(). Suplimentar, trebuie să adăugăm textul LED în buffer pentru a
putea fi afișat.

După toate aceste modificări, fragmentul de cod pentru tratarea mesajului WM_KEYDOWN va arăta astfel:

        case WM_KEYDOWN: { // keyboard key has been pressed

            ++wm_keydown_ctr;

            led_text = "RED";   // BSP_ledRedOn();

            InvalidateRect(me, NULL, FALSE); // force re-paining of the window

            Sleep(200);    // sleep and block for 200 ms

            led_text = "OFF";   // BSP_ledRedOff();


            InvalidateRect(me, NULL, FALSE); // force re-paining of the window

            status = WIN_HANDLED; // report event handled

            break;

    }

Suplimentar, vom adăuga la declarații linia static char const* led_text = "OFF"; iar funcția wsprintf din tratarea mesajului PAINT va arăta
astfel: wsprintf(cBuffer, "KEYBOARD=%3d, MOUSE=%3d, LED=%s ", (wm_keydown_ctr % 1000), (wm_mousemove_ctr % 1000), led_text); pentru ca pe
ecran să fie afișată și informația despre LED.
Hai să rulăm acest program. Când apăsați tastatura o dată starea LED-ului nu se schimbă așa cum era de așteptat, chiar dacă contorul tastaturii crește. Deci,
codul dvs. pentru LED NU funcționează așa cum v-ați imaginat.

Dar situația devine și mai precară. Când apăsați mai multe taste rapid, programul îngheață și nu actualizează imediat contorul tastaturii. Apoi, după o perioadă
de timp, contorul de tastatură sare la o valoare mare.

De fapt, atunci când apăsați mai multe taste sau deplasați mouse-ul, nu se întâmplă nimic în aplicație și niciun contor nu se mărește până când atât contorul
tastaturii, cât și cel al mouse-ului sar la valori noi mari.

Toate acestea, evident, nu sunt bune, deoarece aplicația pare înghețată - nu răspunde. Iar salturile masive din contoarele afișate sunt destul de ciudate. Se pare
că acest comportament este o consecință a evenimentelor asincrone și a cozii de evenimente în Windows.

Problema este că întârzierea Sleep() blochează WndProc și nu permite revenirea rapidă la bucla de evenimente. Mesajele WM_KEYDOWN de la tastatură se
acumulează în coada de evenimente, dar vor putea fi procesate doar după expirarea celor 200 ms. De aici saltul brusc al valorilor contoarelor.

Este o problemă bine cunoscută, iar programatorii PPE i-au dat chiar și un nume:  ei numesc o astfel de aplicație "pig" (porc) și nimeni nu ar vrea să fie numit
așa.

Regulă generală pentru programele Windows este că, dacă sunt necesare mai mult de aproximativ 100 de milisecunde pentru procesarea unui eveniment,
acesta trebuie divizat în câteva evenimente, tratarea fiecăruia având o durată mai mică.

Așadar, blocarea introdusă de Sleep() explică lipsa de reacție și „înghețarea” programului. Dar să ne amintim: actualizarea LED-ului după apăsarea unei taste nu
a funcționat efectiv. Și motivul în acest caz este și mai interesant.

Din perspectiva evenimentului, orice apel de blocare din codul nostru, cum ar fi Sleep(), conduce la așteptarea expirării celor 200 ms. Deblocarea înseamnă că
acest eveniment a avut loc. Evenimentul pe care îl obțineți la deblocare s-ar putea să nu fie numit explicit, dar totuși, acesta este livrat în mijlocul procesării
unui alt eveniment - WM_KEYDOWN în acest caz. Dar acest lucru încalcă semantica Run-to-Completion de procesare a evenimentelor, pe care sistemul
Windows, bazat pe eveniment, și-l asumă.

Mai precis, apelarea funcției InvalidateRect() chiar înainte de blocarea Sleep() nu are efect, deoarece WndProc nu se întoarce la Windows în acest moment. Prin
urmare, Windows nu are nicio șansă să trimită mesajul WM_PAINT la WndProc pentru a actualiza efectiv starea LED-ului. Prin urmare, nu vom vedea niciodată
actualizarea.

În consecință, utilizarea paradigmei de programare secvențială în sistemele bazate pe evenimente, în special când este vorba de blocare, nu este o idee prea
bună din două motive: (1) înfundă bucla evenimentului și distruge receptivitatea programului la toate evenimentele, nu doar la cele care se blochează pentru o
perioadă și (2) încalcă semantica Run-to-Completion asumată universal în toate sistemele bazate pe evenimente.

Aceasta este cea mai importantă concluzie din această lecție de care vreau să vă amintiți: programarea secvențială și programarea bazată pe evenimente sunt
două paradigme distincte care nu se amestecă bine - deci păstrați-le separate.

Last modified: Monday, 4 May 2020, 12:02 AM

◄ Tratarea mesajelor Jump to... Soluția ►

You are logged in as Mihail Curchi (Log out)


PWINDOWS
Data retention summary
Get the mobile app

S-ar putea să vă placă și