Partea întunecată a aplicației. ProcessMessages

Articol trimis de Marcus Junglas

Când programați un handler de evenimente în Delphi (cum ar fi OnClick evenimentul unui TButton), vine momentul în care aplicația trebuie să fie ocupată o perioadă, de ex. codul trebuie să scrie un fișier mare sau să comprimeze unele date.

Dacă faci asta, vei observa asta aplicația dvs. pare blocată. Formularul dvs. nu mai poate fi mutat și butoanele nu arată niciun semn de viață. Se pare că s-a prăbușit.

Motivul este că o aplicație Delpi este single threaded. Codul pe care îl scrieți reprezintă doar o mulțime de proceduri care sunt numite de firul principal al lui Delphi ori de câte ori a avut loc un eveniment. Restul timpului, firul principal este gestionarea mesajelor sistemului și a altor lucruri, cum ar fi funcțiile de gestionare a formei și componentelor.

Așadar, dacă nu terminați gestionarea evenimentelor efectuând o muncă îndelungată, veți împiedica aplicația să gestioneze acele mesaje.

O soluție comună pentru un astfel de tip de probleme este să apelați „Aplicație. ProcessMessages“. „Aplicația” este un obiect global al clasei TApplication.

instagram viewer

Aplicația. Processmessages gestionează toate mesajele în așteptare cum ar fi mișcările ferestrei, clicurile pe butoane și așa mai departe. Este utilizat în mod obișnuit ca o soluție simplă pentru a vă menține "funcționarea" aplicației.

Din păcate, mecanismul din spatele „ProcessMessages” are propriile sale caracteristici, care ar putea provoca confuzii mari!

Ce înseamnă ProcessMessages?

PprocessMessages gestionează toate mesajele sistemului de așteptare din coada de mesaje a aplicațiilor. Windows folosește mesaje pentru a „vorbi” cu toate aplicațiile care rulează. Interacțiunea utilizatorilor este adusă în formular prin mesaje și „ProcessMessages” le gestionează.

Dacă mouse-ul coboară pe un TButton, de exemplu, ProgressMessages face tot ce ar trebui să se întâmple la acest eveniment, cum ar fi revopsirea butonului la o stare „apăsată” și, desigur, un apel la procedura de gestionare OnClick () dacă i-ați atribuit unul.

Aceasta este problema: orice apel către ProcessMessages ar putea conține din nou un apel recursiv la orice handler event. Iată un exemplu:

Utilizați codul următor pentru un handler OnClick chiar și un buton („lucru”). Declarația for simulează o lucrare de procesare îndelungată cu unele apeluri la ProcessMessages din când în când.

Aceasta este simplificată pentru o mai bună lizibilitate:

{în MyForm:}
WorkLevel: număr întreg;
{OnCreate:}
WorkLevel: = 0;
procedură TForm1.WorkBtnClick (Expeditor: TObject);
var
ciclu: număr întreg;
începe
inc (WorkLevel);
pentru ciclu: = 1 la 5 do
începe
Memo1.Lines. Adăugați ('- Work' + IntToStr (WorkLevel) + ', Cycle' + IntToStr (cycle);
Aplicație. ProcessMessages;
somn (1000); // sau alte lucrări
Sfârșit;
Memo1.Lines. Adăugați ('Work' + IntToStr (WorkLevel) + 'încheiat.');
dec (WorkLevel);
Sfârșit;

FĂRĂ „ProcessMessages”, următoarele linii sunt scrise în memorie, dacă butonul a fost apăsat de două ori pe două ori:

 - Munca 1, ciclul 1
- Munca 1, ciclul 2
- Munca 1, ciclul 3
- Munca 1, ciclul 4
- Lucrul 1, ciclul 5
Munca 1 s-a încheiat.
- Munca 1, ciclul 1
- Munca 1, ciclul 2
- Munca 1, ciclul 3
- Munca 1, ciclul 4
- Lucrul 1, ciclul 5
Munca 1 s-a încheiat.

În timp ce procedura este ocupată, formularul nu arată nicio reacție, dar al doilea clic a fost introdus în coada de mesaje de Windows. Imediat după terminarea „OnClick” va fi apelat din nou.

Inclusiv „ProcessMessages”, rezultatul poate fi foarte diferit:

 - Munca 1, ciclul 1
- Munca 1, ciclul 2
- Munca 1, ciclul 3
- Munca 2, ciclul 1
- Munca 2, ciclul 2
- Munca 2, ciclul 3
- Munca 2, ciclul 4
- Lucrul 2, ciclul 5
Munca 2 s-a încheiat.
- Munca 1, ciclul 4
- Lucrul 1, ciclul 5
Munca 1 s-a încheiat.

De data aceasta formularul pare să funcționeze din nou și acceptă orice interacțiune cu utilizatorii. Deci, butonul este apăsat pe jumătate în timpul primei funcții "lucrător", care va fi gestionat instantaneu. Toate evenimentele primite sunt tratate ca orice alt apel funcțional.

Teoretic, în timpul fiecărui apel către „ProgressMessages”, orice cantitate de clicuri și mesaje ale utilizatorului s-ar putea întâmpla „la loc”.

Deci, aveți grijă cu codul dvs.!

Exemplu diferit (în pseudo-cod simplu!):

procedură OnClickFileWrite ();
var myfile: = TFileStream;
începe
myfile: = TFileStream.create ('myOutput.txt');
încerca
in timp ce BytesReady> 0 do
începe
fisierul meu. Scriere (DataBlock);
dec (BytesReady, sizeof (DataBlock));
DataBlock [2]: = # 13; {linia de testare 1}
Aplicație. ProcessMessages;
DataBlock [2]: = # 13; {linia de testare 2}
Sfârșit;
in cele din urma
myfile.free;
Sfârșit;
Sfârșit;

Această funcție scrie o cantitate mare de date și încearcă să „deblocheze” aplicația folosind „ProcessMessages” de fiecare dată când se scrie un bloc de date.

Dacă utilizatorul face clic din nou pe buton, același cod va fi executat în timp ce fișierul este încă scris. Deci fișierul nu poate fi deschis pentru a doua oară și procedura eșuează.

Poate că aplicația dvs. va recupera unele erori, cum ar fi eliberarea de buffere.

Ca rezultat posibil, „Datablock” va fi eliberat și primul cod va ridica „dintr-o dată” o „încălcare a accesului” atunci când va accesa. În acest caz: linia de testare 1 va funcționa, linia de testare 2 se va prăbuși.

Calea mai bună:

Pentru a face mai ușor, puteți seta întregul Formular "activat: = false", care blochează toată intrarea utilizatorului, dar NU îl arată utilizatorului (toate butoanele nu sunt gri).

O modalitate mai bună ar fi să setați toate butoanele pe „dezactivate”, dar acest lucru ar putea fi complex dacă doriți să păstrați un buton „Anulează”, de exemplu. De asemenea, trebuie să parcurgeți toate componentele pentru a le dezactiva și atunci când sunt activate din nou, trebuie să verificați dacă mai rămân unele în starea dezactivată.

Ai putea dezactivați controlul unui copil de container atunci când proprietatea Enabled se modifică.

După cum sugerează numele clasei „TNotifyEvent”, aceasta trebuie utilizată numai pentru reacții pe termen scurt la eveniment. Pentru codul care consumă timp, cel mai bun mod este IMHO de a pune tot codul „lent” într-un fir propriu.

În ceea ce privește problemele cu „PrecessMessages” și / sau activarea și dezactivarea componentelor, utilizarea unui al doilea fir pare să nu fie deloc prea complicat.

Nu uitați că chiar și linii simple și rapide de cod pot atârna câteva secunde, de ex. deschiderea unui fișier pe o unitate de disc ar trebui să aștepte până când terminația de disc a terminat. Nu pare foarte bine dacă aplicația dvs. pare să se prăbușească, deoarece unitatea este prea lentă.

Asta e. Data viitoare când adăugați „Aplicație. ProcessMessages ", gândește de două ori;)