wie nutzt man richtig QThreads bzw Threads ??

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Ernie1412
User
Beiträge: 160
Registriert: Freitag 10. Januar 2020, 20:38

ich will ein filetransfer Fenster erstellen ähnlich wie es vom Explorer ist.
hier mein Ansatz:

Code: Alles auswählen

from PyQt5 import QtWidgets, uic, QtCore
import sys
import time
import shutil
import os


class UpdateCopy(QtCore.QThread):
    update_progressbar = QtCore.pyqtSignal(int)
    def __init__(self, destination_path=None,source_path=None):
        QtCore.QThread.__init__(self)
        self.destination_path=destination_path
        self.source_path=source_path 
        
        def run(self):
            while os.path.getsize(self.source_path) != os.path.getsize(self.destination_path):
                val=int((float(os.path.getsize(self.destination_path))/float(os.path.getsize(self.source_path))) * 100)            
                time.sleep(.01)            
                self.update_progressbar.emit(val)

class Transfer_File(QtWidgets.QDialog):    
    def __init__(self):
        super(Transfer_File,self).__init__()        
        uic.loadUi(Path(__file__).absolute().parent / "ui/Transfer.ui",self)
        self.btn_OK.clicked.connect(self.updateThread)
        
    def updateThread(self):
        source_path=r"D:\\TMW.mp4"
        destination_path=r"E:\\TMW.mp4"                    
        self.lbl_Source.setText(source_path) 
        self.lbl_Desti.setText(destination_path)
        self.prgBar_Transfer.setValue(0)
        self.lbl_Done.setText("Datei übertragt: ")
        self.copy=UpdateCopy(destination_path,source_path)
        self.copy.start()
        shutil.move(source_path, destination_path)
        self.copy.finished.connect(self.evt_update_copy_finnished)
        self.copy.update_progressbar.connect(self.evt_update_progress)

    def evt_update_copy_finnished(self):        
        self.lbl_Done.setText("Fertig!")
        self.prgBar_Transfer.setValue(100)
    
    def evt_update_progress(self,val):
        self.prgBar_Transfer.setValue(val)

if __name__ == '__main__':
    app =QtWidgets.QApplication(sys.argv)
    Transfer_File=Transfer_File()  
    Transfer_File.show()
    sys.exit(app.exec_())
1. der Mauszeiger ist ein "beschäftigt" Modus, wenn ich das Porgramm starte
2. wenn ich auf OK klicke führt er aus meiner Sicht nur das aus:

Code: Alles auswählen

self.lbl_Source.setText(source_path) 
        self.lbl_Desti.setText(destination_path)
        self.prgBar_Transfer.setValue(0)
        self.lbl_Done.setText("Datei übertragt: ")
        self.copy=UpdateCopy(destination_path,source_path)
progressbar macht nichts er kopiert nur bzw verschiebt.

Ich hab einige Videos (morpheus oder https://www.youtube.com/watch?v=G7ffF0U36b0&t=6s) angeschaut, aber nichts passt zu meins. Ich finds schwer zu kapieren, evtl hilft mir da einer und bitte nicht mit irgendeinen Fachchinesisch. Das hilft keinen.
Ernie1412
User
Beiträge: 160
Registriert: Freitag 10. Januar 2020, 20:38

Ergänzend: Ein kleines Fenster mit 3 Qlabels, ein Progressbar und ein Button, mehr nicht
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Ernie1412: Du erstellst den Thread der das kopieren überwachen soll, kopierst dann, und *danach*, *nachdem* kopiert wurde, verbindest Du die Signale über die der Thread den Fortschritt meldet. Selbst das starten des Threads sollte man nach dem Verbinden der Signale machen, denn wenn der zwischen Start und dem verbinden des Finished-Signals fertigs wird, dann hat der das gesendet bevor Dein Code das bekommen hat.

Zudem macht der Thread nichts, weil der keine `run()`-Methode besitzt. Du definierst in der `__init__()` eine `run()`-Funktion mit der nichts gemacht wird. Die sollte wohl als Methode auf das Objekt.

Der Code im ``if __name__ …`` gehört in eine Funktion.

In `Transfer_File` ist ein Unterstrich der da nicht hingehört. Die beiden Klassen heissen wie Funktionen. Und im Hauptprogramm wird der Name `Transfer_File` auch für ein Exemplar von `Transfer_File` verwendet, das ist verwirrend und entspricht nicht den Namenskonventionen.

Bei `exec_()` sollte der Unterstrich verschwinden.

`super()` bekommt keine Argumente mehr in Python 3.

Die Namen aus dem Designer haben Unterstriche *und* Grossbuchstaben, und enthalten kryptische Abkürzungen, und die Worte sind teilweise in der falschen Reihenfolge.

In `updateThread()` wird ein neues Attribut `copy` eingeführt was auf der Klasse nichts zu suchen hat.

Was soll `evt` bedeuten? „Event“? Die Methoden haben nichts mit Events zu tun wie Qt den Begriff verwendet. Das ist verwirrend.

Defaultwerte sollten Sinn ergeben. Die Dateinamen müssen übergeben werden, da kann man nicht einfach einen weglassen und `None` verwenden.

Das heisst `source_path` und `destination_path`, aber dann sind das gar keine `Path`-Objekte und es wird dann doch wieder `os.path` verwendet. Warum mischst Du das?

Die Dateigrössen werden unnötig zweimal ermittelt in der Schleife.

Wenn der Kopiervorgang abbricht, dann endet der Thread niemals, weil die beiden Grössen dann niemals gleich sind.

Allerdings besteht auch eine grosse Chance das der Thread sofort endet, bei dem Versuch die Grösse der Zieldatei zu ermitteln falls die überhaupt noch nicht existiert, weil der Thread ja vor dem `move()` startet. Und am Ende könnte er abschmieren wenn das `move()` die Quelldatei entfernt hat, und versucht wird deren Grösse zu ermitteln. Nebenläufigkeit ist nicht so einfach, man muss an alle möglichen Wettlaufsituationen denken die entstehen können. Der Thread braucht einen Weg um von aussen mitgeteilt zu bekommen, das er die Schleife beenden soll.

Die `float()`-Aufrufe sind überflüssig.

Zwischenstand, der nicht funktionieren wird:

Code: Alles auswählen

#!/usr/bin/env python3
import shutil
import sys
import time
from pathlib import Path

from PyQt5 import QtCore, QtWidgets, uic


class UpdateCopy(QtCore.QThread):
    update_progressbar = QtCore.pyqtSignal(int)

    def __init__(self, destination_path, source_path):
        QtCore.QThread.__init__(self)
        self.destination_path = destination_path
        self.source_path = source_path

    def run(self):
        while True:
            try:
                source_size = self.source_path.stat().st_size
            except IOError:
                source_size = None
            
            try:
                destination_size = self.destination_path.stat().st_size
            except IOError:
                destination_size = None

            if None not in [source_size, destination_size]:
                if source_size == destination_size:
                    break

                self.update_progressbar.emit(
                    int(destination_size / source_size * 100)
                )
                time.sleep(0.01)


class TransferFile(QtWidgets.QDialog):
    def __init__(self):
        super().__init__()
        uic.loadUi(Path(__file__).absolute().parent / "ui/Transfer.ui", self)
        self.btn_OK.clicked.connect(self.copy_file)

    def copy_file(self):
        source_path = Path(R"D:\\TMW.mp4")
        destination_path = Path(R"E:\\TMW.mp4")
        self.lbl_Source.setText(str(source_path))
        self.lbl_Desti.setText(str(destination_path))
        self.prgBar_Transfer.setValue(0)
        self.lbl_Done.setText("Datei übertragt: ")
        copy = UpdateCopy(destination_path, source_path)
        copy.finished.connect(self.on_copying_finished)
        copy.update_progressbar.connect(self.update_progress)
        copy.start()
        shutil.move(source_path, destination_path)

    def on_copying_finished(self):
        self.lbl_Done.setText("Fertig!")
        self.prgBar_Transfer.setValue(100)

    def update_progress(self, value):
        self.prgBar_Transfer.setValue(value)


def main():
    app = QtWidgets.QApplication(sys.argv)
    transfer_file = TransferFile()
    transfer_file.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
Funktioniert `emit()` wenn keine Ereignisschleife läuft? Bin mir da gerade nicht sicher.

Was ohne Ereignisschleife nicht funktioniert ist das Empfangen von Signalen. `move()` blockiert die GUI. Du hast letztlich das falsche in einen Thread gesteckt — das kopieren muss neben der GUI-Hauptschleife passieren. Das abfragen der Dateigrössen kann man locker in einem `QTimer` regelmässig prüfen, ohne da eine lang laufende Schleife draus zu machen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ernie1412 hat geschrieben: Mittwoch 22. Juni 2022, 22:58 Ich finds schwer zu kapieren, evtl hilft mir da einer und bitte nicht mit irgendeinen Fachchinesisch. Das hilft keinen.
Threading ist schwer. Gibt’s nicht für Leute, die sich keine Mühe geben wollen 🤷🏼‍♂️
Ernie1412
User
Beiträge: 160
Registriert: Freitag 10. Januar 2020, 20:38

Threading ist schwer. Gibt’s nicht für Leute, die sich keine Mühe geben wollen 🤷🏼‍♂️
wenn ich damit gemeint bin, danke !!! das hab ich gebraucht.

nun hab ich das video von Tim Woocker https://www.youtube.com/watch?v=Se-YCeqqlAMangeschaut, nur bei mir kommt beim gleichen ähnlichen code "TypeError: 'NoneType' object is not callable"

Code: Alles auswählen

from PyQt5 import QtWidgets, uic, QtCore
import sys
from pathlib import Path
import time
import shutil
import os
import threading

class Transfer_File(QtWidgets.QDialog):    
    def __init__(self):
        super().__init__()        
        uic.loadUi(Path(__file__).absolute().parent / "ui/Transfer.ui",self)
        
        source_path=r"D:\TMW.mp4"
        destination_path=r"E:\TMW.mp4"                            
        self.lbl_Source.setText(source_path) 
        self.lbl_Desti.setText(destination_path)
        self.prgBar_Transfer.setValue(0)
        self.lbl_Done.setText("Datei übertragt: ") 
        self.btn_OK.clicked.connect(lambda: self.start_copy(source_path, destination_path))
        
    def thread(func):
        def wrapper(*args, **kwargs):
            t= threading.Thread(target=func, args=args, kwargs=kwargs)
            t.start()
            return wrapper

    def start_copy(self,source_path, destination_path):
        self.lbl_Done.setText("verschiebe Datei ...")        
        self.copying_file(source_path, destination_path)        
        self.checker(source_path, destination_path)
        
    @thread
    def checker(self,source_path, destination_path):
        # Making sure the destination path exists
        # while not os.path.exists(destination_path):
        #     self.lbl_Done.setText("Datei nicht vorhanden")
        #     time.sleep(.01)            
        # # Keep checking the file size till it's the same as source file
        # while os.path.getsize(source_path) != os.path.getsize(destination_path):
        #     self.prgBar_Transfer.setValue(int((float(os.path.getsize(destination_path))/float(os.path.getsize(source_path))) * 100))            
        #     time.sleep(.01)            
        #     self.lbl_Done1.setText(str(os.path.getsize(destination_path)))
        time.sleep(5)
        self.lbl_Done.setText(source_path, destination_path) 

    @thread    
    def copying_file(self,source_path, destination_path):
        # shutil.move(source_path, destination_path)

        # if os.path.exists(destination_path):
        #     self.lbl_Done.setText("Fertig!")
        #     self.prgBar_Transfer.setValue(100)
        #     return True
        time.sleep(3)
        self.lbl_Done.setText(source_path, destination_path)
        # return False

if __name__ == "__main__":
    app,QtWidgets.QDialog =(QtWidgets.QApplication(sys.argv),Transfer_File())      
    QtWidgets.QDialog.show()
    sys.exit(app.exec_())
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Tja, da fehlt wohl eine Zeile.
Ich würde aber auch keine Threads per Dekorator machen. Das verändern von QT-Elementen innerhalb von Python-Threads ist nicht gut. Um Sicher zwischen Threads und GUIs kommunizieren zu können hat doch Qt sein eigenes Thread-Konzept, das sollte man dann auch nutzen.
`QtWidgets.QDialog` mit der Instanz einer eigenen Klasse zu überschreiben ist eine sehr schlechte Idee, wie kommt man darauf?
Und zwei Zuweisungen durch ein Tuple-Unpacking zu verschmelzen ist wirklich nur dann sinnvoll, wenn die beiden Dinge extrem Eng zusammenhängen, hier ist das einfach nur Verschleierung, um möglichst schlecht Lesbaren Code zu generieren.
Hat Dir schonmal jemand gesagt, dass `exec_` durch `exec` ersetzt worden ist?
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Ernie1412: Bitte kompletten Traceback.
In specifications, Murphy's Law supersedes Ohm's.
Ernie1412
User
Beiträge: 160
Registriert: Freitag 10. Januar 2020, 20:38

return wrapper war falsch eingerückt
jetzt gehts aber ich bekomme dauernd "QObject::setParent: Cannot set parent, new parent is in a different thread"

denke ich muss wohl wieder auf QThread zurück
Das verändern von QT-Elementen innerhalb von Python-Threads ist nicht gut
denke deswegen die Aussage
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die fachchinesischen Begriffe sind "thread ownership" , "queued connections", "worker object" etc. Alles zu finden in der QThread-bezogenen Dokumentation, Beispiele inklusive: https://doc.qt.io/qt-6/qthread.html
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Ich finds schwer zu kapieren, evtl hilft mir da einer und bitte nicht mit irgendeinen Fachchinesisch.
Nebenläufige Programmierung, egal ob mit Threads, Prozessen oder Coroutinen ist nie einfach, weil der Programmablauf halt nicht mehr linear ist und man sich da anders rein denken muss. In Verbindung mit einem GUI-Framework ist es IMHO nochmal eine Stufe schwieriger, weil man die nebenläufige Programmierung mit dem Mainloop der GUI koordinieren muss bzw. im Falle von QThreads integrieren muss.

Hast du Erfahrung mit nebenläufiger Programmierung, also hast du z.B. schonmal was mit dem threading oder concurrent.futures oder asyncio Modul von Python gemacht, ohne GUI?

Gruß, noisefloor
Ernie1412
User
Beiträge: 160
Registriert: Freitag 10. Januar 2020, 20:38

nur asyncio beim webscraping. klappte aber nicht so wie ich wollte und habs verworfen.
mit threading nur videos angeschaut und nachgemacht.
Ich beschäftige mich aber im moment sehr viel mit python, dauerhaft nun schon 3 Monate. Vorher immer mal so Phasen.
ich bin früher am C64 als Kind, Jugendlicher, angefangen zu programmieren. Dann über visual basic weiter und nun python. Alles selber beigebracht, probieren , lesen videos angeschaut usw.
Das scheint hier irgendwie ein Problem zu sein , hab ich das Gefühl :)
Bin nun an einem Punkt gekommen, wo ich nicht nur linear mein Programm aufbauen möchte. Jetzt möchte ich das mein Programm nicht immer komplett blockiert.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich behaupte mal jeder hier hat sich Programmieren selber beigebracht. Entgegen deiner Annahme wird das naemlich nicht in irgendwelchen Uni-Seminaren gelehrt. Da bekommt man nur einen Hinweis, was man bis naechste Woche drauf zu haben hat.

Das Problem hingegen ist deine standhafte Weigerung, sich mit komplexen Dingen auseinander zu setzen. Und stattdessen zu behaupten, diese Hinweise waere aus irgendwelchen niederen Motiven heraus gegeben worden. Wie auch jetzt wieder. "Nur weil ich nicht offiziell Programmierer bin, seid ihr boese zu mir". Was ein Schwachsinn...
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

Programmieren ist halt learning by doing. Ich habe auch weder beruflich noch vom Studium her irgendwas mit IT zu tun. Programmieren mit Python habe ich mir auch selber beigebracht.
IMHO ist Video gucken zum Lernen suboptimal, weil zu passiv. Plus das Lerntempo ist vorgegeben, was dann IMHO bei den meisten nicht passt.
Programmieren lernen heißt halt: Code schreiben, idealerweise Kritik abholen (z.B. hier im Forum), im schlechtesten Fall den bisherigen Code weg werfen und dann neu und besser machen.

Wenn nebenläufige Programmierung neu für dich ist, dann ist der gegebene Fall IMHO ziemlich ambitioniert. Also nicht, dass du das nicht hinbekommen würdest, aber sicherlich wird das nicht über Nacht passieren und nicht im 1. oder 2. oder 3. Anlauf. Tipps, in welche Richtung es gehen sollte, hast du ja schon bekommen.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Ernie1412: Ich habe auch damals als Jugendlicher mit dem C64 autodidaktisch angefangen. BASIC und Assembler auf dem Gerät selbst beigebracht, dann PC mit GW-BASIC, QBasic, Turbo Pascal, und Assembler unter DOS, und auch ein bisschen klassisches VisualBasic als das mit Windows los ging. Habe ja gerade im Thema „Rekursive Funktion“ eine Lösung gezeigt, mit der ersten Programmiersprache mit der ich Kontakt hatte, die Rekursion ermöglicht. Damals COMAL 0.14 von Diskette auf dem C64, heute COMAL 2.0 als Steckmodul für den C64. 🙂

Mein Problem bei Dir ist, dass Du wirklich einfache Sachen wie Namenskonventionen nicht umsetzen willst oder kannst, und auf so verrückte Ideen kommst eine Klasse in einer GUI-Bibliothek durch ein Exemplar davon zu ersetzen. Mit einem Syntaxkonstrukt wo ich auch nicht verstehe wie man darauf kommt. Oder ”Zellen” in Datenbanken ”sperren” durch erstellen von TRIGGER pro Datum. Auf der einen Seite überfordern Dich Wörterbücher die in Wörterbüchern stecken, auf der anderen Seite willst Du nebenläufige Programmierung mit Threads in einem GUI-Rahmenwerk wie Qt umsetzen, was eine Grössenordnung komplexer ist als verschachtelte Grunddatentypen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten