Computerwissenschaften

Die dunkle Seite von Application.ProcessMessages

Artikel eingereicht von Marcus Junglas

Wenn Sie einen Ereignishandler in Delphi programmieren (wie das OnClick- Ereignis eines TButton), muss Ihre Anwendung eine Weile beschäftigt sein, z. B. muss der Code eine große Datei schreiben oder einige Daten komprimieren.

Wenn Sie dies tun, werden Sie feststellen, dass Ihre Anwendung gesperrt zu sein scheint . Ihr Formular kann nicht mehr verschoben werden und die Schaltflächen zeigen kein Lebenszeichen an. Es scheint abgestürzt zu sein.

Der Grund dafür ist, dass eine Delpi-Anwendung Single-Threaded ist. Der Code, den Sie schreiben, stellt nur eine Reihe von Prozeduren dar, die von Delphis Hauptthread aufgerufen werden, wenn ein Ereignis eintritt. Der Rest der Zeit behandelt der Haupt-Thread Systemnachrichten und andere Dinge wie Formular- und Komponentenhandhabungsfunktionen.

Wenn Sie Ihre Ereignisbehandlung nicht durch langwierige Arbeit beenden, verhindern Sie, dass die Anwendung diese Nachrichten verarbeitet.

Eine übliche Lösung für solche Probleme ist der Aufruf von „Application.ProcessMessages“. „Anwendung“ ist ein globales Objekt der TApplication-Klasse.

Die Application.Process-Nachrichten verarbeiten alle wartenden Nachrichten wie Fensterbewegungen, Schaltflächenklicks usw. Es wird häufig als einfache Lösung verwendet, um Ihre Anwendung „funktionsfähig“ zu halten.

Leider hat der Mechanismus hinter „ProcessMessages“ seine eigenen Eigenschaften, die große Verwirrung stiften können!

 

Was macht ProcessMessages?

PprocessMessages verarbeitet alle wartenden Systemnachrichten in der Anwendungsnachrichtenwarteschlange. Windows verwendet Nachrichten, um mit allen laufenden Anwendungen zu „sprechen“. Die Benutzerinteraktion wird über Nachrichten in das Formular gebracht und von „ProcessMessages“ behandelt.

Wenn die Maus beispielsweise auf einen TButton gedrückt wird, führt ProgressMessages alles aus, was bei diesem Ereignis passieren sollte, z. B. das Neulackieren der Schaltfläche in einen „gedrückten“ Zustand und natürlich einen Aufruf der OnClick () -Verarbeitungsprozedur, wenn Sie dies tun eine zugewiesen.

Das ist das Problem: Jeder Aufruf von ProcessMessages kann wieder einen rekursiven Aufruf eines Ereignishandlers enthalten. Hier ist ein Beispiel:

Verwenden Sie den folgenden Code für den OnClick-Even-Handler einer Schaltfläche („work“). Die for-Anweisung simuliert ab und zu einen langen Verarbeitungsjob mit einigen Aufrufen von ProcessMessages.

Dies wird zur besseren Lesbarkeit vereinfacht:


 {in MyForm:}
WorkLevel: integer; 
 {OnCreate:}
WorkLevel:=0;

Prozedur TForm1.WorkBtnClick (Absender: TObject);
var
cycle: integer;
begin
inc (WorkLevel);
für Zyklus:=1 zu 5 do
beginnen
Memo1.Lines.Add ( ‚- Arbeit‘ + IntToStr (Worklevel) + ‚Zyklus‘ + IntToStr (Zyklus);
Application.ProcessMessages;
Schlaf (1000); // oder eine andere Arbeit
end ;
Memo1.Lines.Add (‚Work‘ + IntToStr (WorkLevel) + ‚beendet.‘);
dec (WorkLevel);
end ;

OHNE „ProcessMessages“ werden die folgenden Zeilen in das Memo geschrieben, wenn die Taste in kurzer Zeit ZWEIMAL gedrückt wurde:


- Arbeit 1, Zyklus 1 
- Arbeit 1, Zyklus 2 
- Arbeit 1, Zyklus 3 
- Arbeit 1, Zyklus 4 
- Arbeit 1, Zyklus 5 
Arbeit 1 beendet. 
- Arbeit 1, Zyklus 1 
- Arbeit 1, Zyklus 2 
- Arbeit 1, Zyklus 3 
- Arbeit 1, Zyklus 4 
- Arbeit 1, Zyklus 5 
Arbeit 1 beendet.

Während die Prozedur ausgeführt wird, zeigt das Formular keine Reaktion an, aber der zweite Klick wurde von Windows in die Nachrichtenwarteschlange gestellt. Unmittelbar nach Beendigung des „OnClick“ wird er erneut aufgerufen.

EINSCHLIESSLICH „ProcessMessages“ kann die Ausgabe sehr unterschiedlich sein:


- Arbeit 1, Zyklus 1 
- Arbeit 1, Zyklus 2 
- Arbeit 1, Zyklus 3 
- Arbeit 2, Zyklus 1 
- Arbeit 2, Zyklus 2 
- Arbeit 2, Zyklus 3 
- Arbeit 2, Zyklus 4 
- Arbeit 2, Zyklus 5 
Arbeit 2 beendet. 
- Arbeit 1, Zyklus 4 
- Arbeit 1, Zyklus 5 
Arbeit 1 beendet.

Dieses Mal scheint das Formular wieder zu funktionieren und akzeptiert jegliche Benutzerinteraktion. Die Taste wird also während Ihrer ersten „Arbeiter“ -Funktion WIEDER zur Hälfte gedrückt, was sofort erledigt wird. Alle eingehenden Ereignisse werden wie jeder andere Funktionsaufruf behandelt.

Theoretisch kann bei jedem Aufruf von „ProgressMessages“ JEDE Anzahl von Klicks und Benutzernachrichten „an Ort und Stelle“ auftreten.

Seien Sie also vorsichtig mit Ihrem Code!

Anderes Beispiel (in einfachem Pseudocode!):


 procedure OnClickFileWrite (); 
 var myfile:=TFileStream; 
 begin
myfile:=TFileStream.create ('myOutput.txt'); 
   versuchen , 
 während BytesReady> 0 do 
 beginnen
myfile.Write (Datenblock); 
   dec (BytesReady, sizeof (DataBlock)); 
   DataBlock [2]:=# 13; {Testzeile 1} 
   Application.ProcessMessages; 
   DataBlock [2]:=# 13; {Testzeile 2} 
 end ; 
   endlich
  myfile.free; 
   Ende ; 
 Ende ;

Diese Funktion schreibt eine große Datenmenge und versucht, die Anwendung jedes Mal, wenn ein Datenblock geschrieben wird, mithilfe von „ProcessMessages“ zu „entsperren“.

Wenn der Benutzer erneut auf die Schaltfläche klickt, wird derselbe Code ausgeführt, während die Datei noch geschrieben wird. Daher kann die Datei nicht ein zweites Mal geöffnet werden und der Vorgang schlägt fehl.

Möglicherweise führt Ihre Anwendung eine Fehlerbehebung durch, z. B. das Freigeben der Puffer.

Als mögliches Ergebnis wird „Datablock“ freigegeben und der erste Code löst „plötzlich“ eine „Zugriffsverletzung“ aus, wenn er darauf zugreift. In diesem Fall: Testlinie 1 funktioniert, Testlinie 2 stürzt ab.

Der bessere Weg:

Zur Vereinfachung können Sie das gesamte Formular auf „enabled:=false“ setzen, wodurch alle Benutzereingaben blockiert werden, dies dem Benutzer jedoch NICHT angezeigt wird (alle Schaltflächen sind nicht grau).

Ein besserer Weg wäre, alle Schaltflächen auf „deaktiviert“ zu setzen. Dies kann jedoch komplex sein, wenn Sie beispielsweise eine Schaltfläche „Abbrechen“ beibehalten möchten. Außerdem müssen Sie alle Komponenten durchgehen, um sie zu deaktivieren, und wenn sie wieder aktiviert werden, müssen Sie überprüfen, ob noch einige im deaktivierten Zustand vorhanden sein sollten.

Sie können untergeordnete Containersteuerelemente deaktivieren, wenn sich die Enabled-Eigenschaft ändert .

Wie der Klassenname „TNotifyEvent“ andeutet, sollte er nur für kurzfristige Reaktionen auf das Ereignis verwendet werden. Für zeitaufwändigen Code ist IMHO der beste Weg, den gesamten „langsamen“ Code in einen eigenen Thread zu stellen.

In Bezug auf die Probleme mit „PrecessMessages“ und / oder das Aktivieren und Deaktivieren von Komponenten scheint die Verwendung eines zweiten Threads überhaupt nicht zu kompliziert zu sein.

Denken Sie daran, dass selbst einfache und schnelle Codezeilen möglicherweise Sekunden lang hängen bleiben. Beispielsweise muss das Öffnen einer Datei auf einem Laufwerk möglicherweise warten, bis das Hochfahren des Laufwerks abgeschlossen ist. Es sieht nicht sehr gut aus, wenn Ihre Anwendung abstürzt, weil das Laufwerk zu langsam ist.

Das ist es. Wenn Sie das nächste Mal „Application.ProcessMessages“ hinzufügen, überlegen Sie es sich zweimal;)

Similar Posts

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.