scheinbares Einfrieren der Hauptform bei langen Berechnungen in Subroutinen
-
Hallo Zusammen,
ich möchte einige langatmige Berechnungen durchführen.
Um den Fortschritt bei den Berechnungen zu sehen habe ich einen Gauge sowie einen aufzubauenden Treeview in die Form (es gibt nur diese eine) eingefügt.Wenn ich nun auf den Button "Starte Berechnung" klicke, fängt das Programm auch brav an die Berechnungen durchzuführen. Dies passiert nur innerhalb der Buttonprozedur.
Einige Sekunden lang wird auch brav die Form, bzw. der Gauge aktualisiert, aber nach wenigen Sekunden sieht es so aus, als ob die Form einfriert und man bekommt als Mauscursor den Wartekringel (Früher Stundenglas). Die CPU-Load zeigt aber, dass die Berechnung weitergeht, und wenn man lange genug wartet wird sie auch beendet, der Gauge springt auf 100 und der fertige Treeview ist da.Frage: wie kann ich es einrichten, dass während derlänger dauernden Berechnungen die Hauptform immer mal wieder aktualisiert wird?
EntwicklungsOS: Windows 10 x64
ZielOS: Windows 10 x32
Embarcadero c++ builder 10.2 32bit deutschViele Grüße,
R
-
Die Berechnung in einen Thread (oder
std::async
) auslagern und das Ergebnis dem Fenster iwie das mitteilen (Callback, etc.).
Du musst dann aber sicherstellen, dass die asynchrone Berechnung beendet wird, wenn das Hauptfenster geschlossen oder das Programm beendet wird, bevor die Berechnung fertig ist.
-
Um Aktualisierungen im Main-Thread aus einem anderen Thread heraus durchzuführen gibt es dazu die Methode TThread::Synchronize.
-
Danke für die Antworten.
Die Mühe mit dem Auslagern wollte ich mir eigentlich sparen. Ich hatte auf etwas in der Art Form1->Refresh oder Form1->Update gehofft.LG
R
-
Gibt es Application->ProcessMessages() nicht mehr? Das habe ich beim BCB6 verwendet, wenn ich zu faul war einen Thread zu erstellen...
-
@Killer-Kobold sagte in scheinbares Einfrieren der Hauptform bei langen Berechnungen in Subroutinen:
Gibt es Application->ProcessMessages() nicht mehr? Das habe ich beim BCB6 verwendet, wenn ich zu faul war einen Thread zu erstellen...
Das gibt´s noch, aber da muss man schon verstehen, wie Windows und dessen Event Queues funktionieren. Ich halte
Application->ProcessMessages()
für gefährlich und man sollte es besser nicht einsetzen.
Beispiel:
Eine langwierige Berechnug soll am Ende ihr Ergebnis in einer Textbox oder was auch immer anzeigen. Die Berechnung findet im Hauptthread der Anwendung statt, weswegen die GUI nicht aktualisiert wird. Also wird während der BerechnungApplication->ProcessMessages()
aufgerufen, damit die GUI sich aktualisieren kann. Wenn der Benutzer aber das Fenster schließt während läuft die Berechnung endet das in undefiniertem Verhalten. Die Berechnung will ihr Ergebnis im Fenster darstellen, das es nicht mehr gibt, und greift dann möglicherweise auf ungültigen Speicher zu -> BOOM!String lengthy_calculation() { ... Application->ProcessMessages(); ... return something; } class TMyForm : public TForm { ... private: void __fastcall OnClickButtonCalculate() { auto result = do_lengthy_calculation(); TextBox->Text = "Das Ergbnis ist " + result; } };
Der Benutzer klickt auf den Berechnen Button und die langwierige Berechnung startet. Kurz darauf klickt der Benutzer auf den Schließen-Button des Fensters und ein WM_CLOSE wird in die Queue des Fensters eingefügt. Iwann werden innerhalb der
do_lengthy_calculation()
Funktion die Fensternachrichten behandelt, weilApplication->ProcessMessages
das nun mal macht. Das Fenster behandelt dann iwann seine WM_CLOSE Nachricht, schließt sich und räumt seine Resourcen auf.do_lengthy_calculation()
ist iwann fertig und kehrt zum Aufrufer zurück. Zu diesem Zeitpunkt existieren das TMyForm Formular und damit auch TMyForm.TextBox nicht mehr, und sämtliche Zugriffe auf eins der beiden Objekte erzeugen UB.
Gefährlich. Sehr gefährlich. Finger weg!
-
Hallo Doc,
im Prinzip alles richtig, aber manchmal ist die korrekte Ausführung einfach 'mit Kanonen auf Spatzen schiessen'. Seiteneffekte muss man da in Kauf nehmen.
Wobei man die Problemmatik relativ einfach umgehen kann. Ein Flag beim Start der Berechnung setzen und am Ende löschen und in der CanClose() des Forms das Schließen verweigern, solange das Flag gesetzt ist. Dann noch dafür sorgen, dass der User, so lange die Berechnung läuft, keine Buttons oder sonstige Aktionen durchführen kann und man sollte auf der sicheren Seite sein.
Ich will damit nicht sagen, dass man das grundsätzlich so machen soll, aber manchmal lohnt sich der Aufwand nicht, einen eigenen Thread zu erstellen. Insbesondere, da der Builder meiner Meinung nach eklatante Schwächen bei der TThread-Klasse hat. Zumindest im BCB 6 noch. Schon wenn man nur einen Thread ohne automatisches Löschen (FreeOnTerminate = false) verwendet und das Programm beenden will und versucht den Thread zu beenden bekommt man das zu spüren. Das Beenden kann dann 30 bis 40 Sekunden dauern und dabei wird ein Kern komplett ausgelastet. Wenn man wirklich Kontrolle über den Thread haben will, kommt man nicht darum herum, zum Teil die WinAPI zu bemühen (zB Semaphores).
Wie gesagt, prinzipiell ist richtig was Du schreibst, aber Ausnahmen bestätigen die Regel.
-
@Killer-Kobold Das kann ich nicht bestätigen, ich habe eine Software programmiert, die mehrere TThread Instanzen benutzt, und die beenden sich problemlos ohne irgendwelche Wartezeiten, wenn sie es sollen. Und warum sollte man denn die WinAPI nicht bemühen, letztendlich machen es alle Bibliotheken, die das kapseln, ja auch.
-
@Burkhi : Ich hatte jedenfalls vor Urzeiten im BCB6 massive Probleme damit. Da habe ich damals hier sogar zu gepostet:
https://www.c-plusplus.net/forum/topic/167833/tthread-problem-mit-terminate-direkt-gefolgt-von-waitfor