Discussion:
Application.ProcessMessages
(zu alt für eine Antwort)
Manfred Polak
2017-06-18 20:15:43 UTC
Permalink
Moin!

Ich habe in einem VCL-Programm eine Schleife, deren Abarbeitung mehrere
Minuten dauern kann. Deshalb wird bei jedem 500000. Schleifendurchlauf
neben ein paar anderen Dingen auch ein Application.ProcessMessages
ausgeführt. Der eigentliche Zweck davon ist. dass ein "Abbrechen"-Button
sofort seine Wirkung entfaltet, und das funktioniert auch perfekt.

Als Nebeneffekt kann ich auch das Fenster verschieben etc., während das
Programm rödelt und einen CPU-Kern zu 100% auslastet, und die Symbole
in der Titelleiste zum Maximieren und Minimieren tun ihre Wirkung. Nur
das Symbol zum Schließen funktioniert nicht, während die Schleife läuft.
Wenn ich es anklicke (oder Alt+F4 drücke), passiert erst mal nichts.
Erst wenn die Schleife durch ist (oder wenn ich danach auf "Abbrechen"
klicke), wird das Programm beendet.

Dieses Verhalten ist für mich kein Problem, aber mich interessiert,
warum das so ist, und ob man es ohne Aufwand ändern kann.


Manfred
Sven Lanoster
2017-06-19 00:42:39 UTC
Permalink
Moin!

[Warum funktioniert ALT+F4 mit Application.ProcessMessages nicht]

Windowsprogramme arbeiten Nachrichtenbasiert (Windows-Messages). Jedes
Programm hat (in TApplication) eine Endlosschleife, welche die
Nachrichten abruft und verteilt. Dieses Abrufen und Verteilen kann mit
Application.ProcessMessages auch manuell aufgerufen werden.

Es gibt bei dir also eine Message, welche deine Verarbeitung starten
soll. Zum Beispiel den Klick auf den Start-Button. Diese Message wird in
der Hauptschleife abgerufen und die StartKlick-Methode des Buttons
aufgerufen. Die Hauptschleife steht jetzt, bis der Aufruf zurückkehrt.
Bis dahin reagiert das Programm nicht mehr auf Messages.

Wenn Du dann Application.ProcessMessages in der Schleife aufrufst,
werden die Messages einmal alle abgerufen und verteilt. Anschließend
springt die Programmausführung wieder in die Schleife. Die Hauptschleife
ist immernoch blockiert und wartet auf Beendigung der Schleife.

Das kann lustige Nebeneffekte haben. Wenn der Anwender zum Beispiel aus
Ungeduld erneut den Startknopf drückt, wird deine Schleife erneut
gestartet. Dann wartet die Hauptschleife darauf, dass deine Schleife
fertig wird, die darauf wartet, dass die zum zweiten Mal gestartete
Schleife fertig ist. Das kann man mit einem globalen Flag
(StartButton.enabled := false;) leicht entschärfen.

Auch schön, wenn der Anwender während der Berechnung auf dem Form
Vorgaben für die Berechnung ändert. Dagegen hilft natürlich alle
Controls auf dem Form auszuschalten. Vorsicht, den Abbrechen-Button
besser nicht ausschalten. Außerdem muss man beim Einschalten am Ende
vorsichtig mit der Reihenfolge sein.

Wenn der Anwender ALT+F4 drückt, wird die Message WM_QUIT gesendet und
dadurch Application.Terminated auf True gesetzt. Die Hauptschleife hat
Terminated als Abbruchbedingung und beendet sich. Dadurch kehrt
Application.Run (steht im DPR) zurück und das Programm endet.

Da die Hauptschleife jedoch gar nicht läuft, kehrt Application.Run nicht
zurück bis deine Schleife fertig ist. Deine Schleife muss sich also
beenden, wenn Application.Terminated auf True steht.

Es gibt Fälle, in denen es sinnvoller ist, längere Berechnungen in einen
Thread auszulagern. Allerdings gibt es bei Threads ebenfalls ganz viele
Fallen (insbesondere mit Messages), Deadlocks und Probleme beim Zugriff
auf globale Ressourcen. Abgesehen davon, dass man die VCL nicht benutzen
darf (ist nicht Thread-safe).

Wenigstens kann man sich bei Thread-Problemen mit dem ausgedruckten
Quellcode für ein paar Stunden in die Badewanne setzen. Haltepunkte und
Einzelschrittdebugging sind kaum hilfreich.

Gruß,
Sven.
--
Seltsam? Aber so steht es geschrieben...
Manfred Polak
2017-06-19 16:33:27 UTC
Permalink
Post by Sven Lanoster
[Warum funktioniert ALT+F4 mit Application.ProcessMessages nicht]
Das kann lustige Nebeneffekte haben.
Ich hab schon hier [1] etwas über die Gefahren von Application.Process-
Messages gelesen, aber ich denke, dass ich die Fallstricke vermieden
habe. Während die Schleife läuft, sind nur zwei Buttons enabled, der
zum Abbrechen und einer, der ein nichtmodales Info-Fenster öffnet,
das dem Rest des Programms nicht in die Quere kommt.

[1] https://www.thoughtco.com/dark-side-of-application-processmessages-1058203
Post by Sven Lanoster
Außerdem muss man beim Einschalten am Ende vorsichtig mit der Reihenfolge sein.
Das habe ich jetzt noch nicht verstanden. Wenn die Schleife läuft, werden
der Start-Button, ein Edit und ein paar Checkboxen, Radiobuttons und Labels
deaktiviert, und am Ende wieder auf enabled gesetzt, und zwar (bis jetzt)
in ziemlich willkürlicher Reihenfolge. Was kann denn dabei schiefgehen?
Bis jetzt habe ich dabei jedenfalls nichts Ungewöhnliches bemerkt.
Post by Sven Lanoster
Wenn der Anwender ALT+F4 drückt, wird die Message WM_QUIT gesendet und
dadurch Application.Terminated auf True gesetzt. Die Hauptschleife hat
Terminated als Abbruchbedingung und beendet sich. Dadurch kehrt
Application.Run (steht im DPR) zurück und das Programm endet.
Da die Hauptschleife jedoch gar nicht läuft, kehrt Application.Run nicht
zurück bis deine Schleife fertig ist. Deine Schleife muss sich also
beenden, wenn Application.Terminated auf True steht.
Danke für die ausführliche Erklärung!


Manfred
Sven Lanoster
2017-06-20 07:45:10 UTC
Permalink
Post by Manfred Polak
Post by Sven Lanoster
Außerdem muss man beim Einschalten am Ende vorsichtig mit der Reihenfolge sein.
Das habe ich jetzt noch nicht verstanden. Wenn die Schleife läuft,
werden der Start-Button, ein Edit und ein paar Checkboxen,
Radiobuttons und Labels deaktiviert, und am Ende wieder auf enabled
gesetzt, und zwar (bis jetzt) in ziemlich willkürlicher Reihenfolge.
Was kann denn dabei schiefgehen? Bis jetzt habe ich dabei jedenfalls
nichts Ungewöhnliches bemerkt.
Ich hatte mal ein Projekt, das mit Application.ProcessMessages nur so
gespickt war und sehr umfangreich war. Also viele zig Forms. Zum Glück
waren alle Controls abgeleitet, es wurde also überall der gleiche
StartButton verwendet, so dass ich eine globale Stelle hatte, an der ich
eingreifen konnte.

Meine erste Idee war, einfach das Form auszuschalten. Dann sieht man
jedoch nicht, dass die Controls inaktiv sind.
Die zweite Idee war, das Controls[]-Array im Form zu nutzen und alle
Controls ein-/auszuschalten. Das funktioniert nicht, wenn vor dem Start
Controls absichtlich inaktiv sind und nach dem Durchlauf immernoch
inaktiv sein sollen.
Also habe ich mir in einer Liste die Controls gemerkt, die ich
deaktiviert habe und nur diese nach dem Durchlauf wieder aktiviert.

Das hat bei komplexen Forms nicht funktioniert, bei denen es
geschachtelte Abhängigkeiten gab. Also ein Radiobutton schaltet eine
Groupbox an, die wieder Radiobuttons enthält, die Edits schalten. Ich
erinnere mich leider nicht mehr an das exakte Problem und bekomme es
auch nicht mehr reproduziert.

Nachdem ich die Controls in umgekehrter Reihenfolge aktiviert hatte
(also meine Liste vom Ende zum Anfang durchlaufen bin), hat erstmal
alles funktioniert.

Ich erinnere mich dunkel und ungern, dass ich noch ein paar Sonderfälle
berücksichtigen musste (die DB-Komponente auszuschalten, wenn man damit
arbeiten will, ist nicht so schlau), aber so im groben war das die Lösung.

Gruß,
Sven.
--
Seltsam? Aber so steht es geschrieben...
Manfred Polak
2017-06-20 20:48:58 UTC
Permalink
Post by Sven Lanoster
Das hat bei komplexen Forms nicht funktioniert, bei denen es
geschachtelte Abhängigkeiten gab. Also ein Radiobutton schaltet eine
Groupbox an, die wieder Radiobuttons enthält, die Edits schalten.
OK, sowas Kaskadiertes gibt es in meinem Programm nicht. Da sind
die relevanten Controls (die der Anwender beeinflussen kann) alle
direkt unter Form1 aufgereiht.


Manfred
Michael Landenberger
2017-06-20 10:27:40 UTC
Permalink
Post by Sven Lanoster
Es gibt Fälle, in denen es sinnvoller ist, längere Berechnungen in einen
Thread auszulagern. Allerdings gibt es bei Threads ebenfalls ganz viele
Fallen (insbesondere mit Messages), Deadlocks und Probleme beim Zugriff auf
globale Ressourcen. Abgesehen davon, dass man die VCL nicht benutzen darf
(ist nicht Thread-safe).
Man darf die VCL schon benutzen, muss VCL-Funktionen aber über
TThread.Synchronize aufrufen.

Gruß

Michael
Sven Lanoster
2017-06-21 21:50:12 UTC
Permalink
[Threads]
Post by Michael Landenberger
Post by Sven Lanoster
Abgesehen davon, dass man die VCL nicht benutzen darf
(ist nicht Thread-safe).
Man darf die VCL schon benutzen, muss VCL-Funktionen aber über
TThread.Synchronize aufrufen.
Guter Hinweis, vielen Dank.

Und einfache, übersichtliche Klassen wie zum Beispiel TList kann man
auch benutzen.

Im Thread eigene Forms zu bauen, sollte man sich jedoch verkneifen (oder
wissen, was man tut).

Gruß,
Sven.
--
Seltsam? Aber so steht es geschrieben...
Loading...