Generelle Architektur / Design einer PyQt-Anwendung ?

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
ThomasDD
User
Beiträge: 14
Registriert: Mittwoch 29. November 2017, 11:24

Hallo,

irgendwie fehlt mir da als absoluter Anfänger die Grundlage, gibt es ev. eine allgemeine Doku wo das erklärt wird ?
Ich schreibe mal wie ich es bisher verstanden habe:

Die Anwendung hat eine Main-Funktion, die nur dazu da ist, genau eine Instanz einer QApplication zu erzeugen, die wiederum genau ein Mainwindow erzeugt und anzeigt. Alles weitere (Initialisierungen, der eigentliche Programmablauf) wird dann nicht in dieser Main-Funtion gemacht.

Die eigentliche Main-Schleife wie man sie von 'normalen' C-Programmen her kennt ist das Mainwindow. Dort ist alles Ereignisgesteuert, Mausclicks, Timer etc.
Braucht man nun dennoch eine Art Main-Schleife, z.B. um blockierende Funktonen (Netzwerk,I/O) oder spez. initialisierungen (Downloads etc.) durchzuführen, kann man einen Thread starten, der über Signale/Slots mit dem Mainwindow kommuniziert.

Ist das soweit richtig verstanden, zumindest wenn man erst mal nur ganz einfache Dinge machen will ?

Gibt es eine allg. Doku zu dem ganzen Thema, nicht für einzelne Komponenten oder Aspekte, sondern so eine Art "Best Practice" für GUI-Anwendungen ?

Das Model-View-Konzept muss ich mir noch ansehen, das löst aber m.E. nach auch nicht die o.g. beiden beispielhaften Anforderungen an den Thread, sondern dient nur der dynamischen Änderung der Anzeige.

VG
Thomas
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Streng genommen stimmt das nicht. Du kannst einen Mainloop in QT auch ohne GUI haben. Insofern sehe ich auch nicht wie sich das jetzt von C beziehungsweise in diesem Fall C++ überhaupt unterscheidet. Was das warten auf nebenläufige oder blockierende aufrufe angeht – auch das kann man anders lösen, nämlich durch asynchrone Programmierung. Zum Beispiel die QSocketNotifier. Ähnliche Abstraktionen gibt es auch für andere IO Operationen.

Bücher oder tutorials zu solchen Allgemeinkonzepten kenne ich leider nicht. Persönlich habe ich mir das durch viele Jahre arbeiten an solchen und anderen Anwendungen erworben. Allerdings ist die Dokumentation von QT finde ich im Allgemeinen ziemlich brauchbar. Und ich kann mir auch gut vorstellen, dass sie einführende oder allgemeine Artikel enthält. Gibt zum Beispiel auch ein Blog, in dem meine Firma schon mal gefeatured wurde.
ThomasDD
User
Beiträge: 14
Registriert: Mittwoch 29. November 2017, 11:24

Erstmal Danke für die Tipps, ja man liest am Anfang auch nicht gleich komplett alle Dokus durch, sondern wurstelt schon erstmal los...

Genau den Eindruck dass es in main genau wie bei C/C++-Main läuft hatte ich eben nicht.
Wenn ich in der __main__ nach Aufruf der .show() Funktion z.B. eines QMainWindow noch eine Funktion einfüge, die etwas initialisiert was etwas dauert, dann wird das MainWindow erst angezeigt wenn diese Funktion fertig ist. Deshalb habe ich das in einen Thread ausgelagert, der ein Signal vom MainWindow bekommt, danach die Initialisierung ausführt und die Rückmeldung dass er fertig ist wiederum mit einem Signal meldet dessen Text dann per statusBar angezeigt wird. Das ist mir nur rein in der main so nicht gelungen, das MainWindow wurde immer erst angezeigt wenn diese Initialisierung fertig war.

Ich hatte die QMainWindow auch so verstanden dass das generell ein einzelner Thead ist, der alles andere blockiert wenn irgendeine seiner Funktionen etwas bearbeitet. Selbst wenn man z.B. mit QSocketNotifier arbeitet, wird alles andere blockiert solange man dessen Rückmeldungen verarbeitet.
Das dauert bei mir etwas, es sind viele Sockets, danach Datenbankverarbeitung etc, ehe dann mal etwas davon angezeigt werden soll, und das wollte ich eben nicht alles in eine Funktion von dem MainWindow hineinpacken.

Ausserdem hat der Prozessor damit auch gut zu tun, auch da ist es mir noch nicht klar, ob man bestimmte Aufgaben gezielt von einem anderen Prozessorkern bearbeiten lassen kann oder sollte, oder ob das der jeweilige OS-Kernel sowieso automatisch macht.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

ja auch nach dem show() wird noch Code ausgeführt. Aber noch mal: das ist auch in C++ so. Erst wenn der Main Loop betreten wird, wird das Fenster gezeichnet und die Anwendung startet richtig.

Ob deine Anwendungen mit dem QSocketNotifier ausreichend Performant ist oder nicht, kann ich nicht wirklich beurteilen. Sei dir aber bewusst, dass in Python eine Anwendungen sowieso immer nur einen einzigen aktiven Thread hat. Stichwort: GIL.

Natürlich hast du die Möglichkeit Echte Parallelität mittels multiprocessing zu erreichen.

Und bezüglich des Schedulings auf irgendwelche Core – lass das mal die Sorge das Betriebssystems sein.
Zuletzt geändert von __deets__ am Sonntag 7. Januar 2018, 14:18, insgesamt 2-mal geändert.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

http://doc.qt.io/qt-5/gettingstartedqt.html

Auch hier im Minimalbeispiel in C++ gibts show() gefolgt von exec(). Dazwischen kannst du beliebig Zeit vertrödeln & kein Fenster zeigt sich.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und noch mal zu deiner generellen Architektur: du machst das schon ganz richtig, mit dem auslagern in einen separaten Thread . Und auch in Python ist das besser als gar nix. Denn selbst wenn dein Hintergrundthread schwer arbeitet, kommt gelegentlich mal der Mainthread dran. Aber aufgrund des GILs nicht so oft beziehungsweise nicht immer genau dann, wenn etwas passiert, dass seine Aufmerksamkeit benötigt. Du kannst also in Python die GUI auch aushungern wenn du Hintergrundthreads benutzt. Da hilft dann nur kleinere Arbeitspakete zu schnüren, die man ab arbeitet. Oder eben multi processing.
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

Wobei man beachten sollte, dass der GIL nur dann problematisch ist, wenn man etwas tut, das CPU-bound ist. Gerade bei Sockets sollte das nicht der Fall sein: Wenn der Socket auf I/O wartet, dann wird der GIL freigegeben und ein anderer Thread kann Python-Code ausführen. Auch bei Datenbankzeug kann der GIL freigegeben werden (da müsste man auf die verwendete Anbindung gucken, ob die das macht).

Fazit: “Threads are great at doing nothing.” (David Beazley). Wobei „nothing“ hier halt heißt, keinen Bytecode auszuführen bzw. keine Python-Objekte zu verändern.
Antworten