Seite 1 von 1

PyQt5 Signale und Slots

Verfasst: Montag 28. März 2022, 18:16
von Welpe
Moin,
ich versuche mich an einer GUI für einen Dateidownload. Wie zu erwarten friert die GUI wärend des Downloads ein. Habe dann hier im Forum gestöbert und bin in einem Thread auf diesen Link gestoßen https://realpython.com/python-pyqt-qthread/. Ich verstehe die dort geschilderte Funktionsweise mit dem auslagern des 'Long running task' in einen Worker-Thread und die Signale von dort zurück in den Window Thread.

Was mir nicht einleuchtet, wie schicke ich Signale vom Window Thread an den Worker Thread? Ich verstehe das senden ansich schon, nur werden die Slots im Window Thread in Methoden abgearbeitet. Der Worker-Thread hat aber nur die 'run()' Methode, in der der 'Long running task' läuft. Kann ich da auch beliebig neue Methoden erstellen?

Um meine Frage zu veranschaulichen, hier mal der Code aus dem Link oben.
Wenn ich nun in der Worker Klasse das "for i in range(5)" in ein "for i in range(x)" umschreiben möchte und dann das x aus der Window Klasse an die Worker Klasse übergeben möchte, wie mache ich das?

Code: Alles auswählen

import sys
from time import sleep
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

# Step 1: Create a worker class
class Worker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(int)

    def run(self):
        """Long-running task."""
        for i in range(5):
            sleep(1)
            self.progress.emit(i + 1)
        self.finished.emit()

class Window(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.clicksCount = 0
        self.setupUi()

    def setupUi(self):
        self.setWindowTitle("Freezing GUI")
        self.resize(300, 150)
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        
        # Create and connect widgets
        self.clicksLabel = QLabel("Counting: 0 clicks", self)
        self.clicksLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.stepLabel = QLabel("Long-Running Step: 0")
        self.stepLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.countBtn = QPushButton("Click me!", self)
        self.countBtn.clicked.connect(self.countClicks)
        self.longRunningBtn = QPushButton("Long-Running Task!", self)
        self.longRunningBtn.clicked.connect(self.runLongTask)
        
        # Set the layout
        layout = QVBoxLayout()
        layout.addWidget(self.clicksLabel)
        layout.addWidget(self.countBtn)
        layout.addStretch()
        layout.addWidget(self.stepLabel)
        layout.addWidget(self.longRunningBtn)
        self.centralWidget.setLayout(layout)

    def countClicks(self):
        self.clicksCount += 1
        self.clicksLabel.setText(f"Counting: {self.clicksCount} clicks")

    def reportProgress(self, n):
        self.stepLabel.setText(f"Long-Running Step: {n}")

        # Snip...
    def runLongTask(self):
        # Step 2: Create a QThread object
        self.thread = QThread()

        # Step 3: Create a worker object
        self.worker = Worker()

        # Step 4: Move worker to the thread
        self.worker.moveToThread(self.thread)

        # Step 5: Connect signals and slots
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.worker.progress.connect(self.reportProgress)
        
        # Step 6: Start the thread
        self.thread.start()

        # Final resets
        self.longRunningBtn.setEnabled(False)
        self.thread.finished.connect(
            lambda: self.longRunningBtn.setEnabled(True)
        )
        self.thread.finished.connect(
            lambda: self.stepLabel.setText("Long-Running Step: 0")
        )

app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())

Re: PyQt5 Signale und Slots

Verfasst: Montag 28. März 2022, 18:50
von __deets__
Grundsätzlich sind alle Methoden auf dem Worker ja auch slots, und entsprechend können die auch per queued connection aus dem main Thread getriggert werden. Aber damit das funktioniert muss der thread in seinem exec die eigenen Ereignisschleife abarbeiten. Das gut er nach Anlage, aber wenn du dann das run (übrigens ganz beschissen benannt durch den Autor der Seite, denn das ist gleich der run Methode im thread selbst, und die sollte man nicht überladen, und auch den worker so nicht nennen, um Verwirrung zu vermeiden. work wäre passend) via started connection aufgerufen wird, kehrt das ja erst wieder zurück, wenn die Arbeit komplett ist. Mit anderen Worten: der leidet unter dem gleichen Phänomen wie der main Thread, wenn da etwas lange läuft. Erst wenn run beendet ist, kommen folgende slots zum Zug.

Re: PyQt5 Signale und Slots

Verfasst: Dienstag 29. März 2022, 17:40
von Welpe
Danke deets, das hilft mir schonmal ein wenig weiter. Muß ich mal schauen ob ich dieses Phänomen irgendwie umschiffen kann.

Re: PyQt5 Signale und Slots

Verfasst: Dienstag 29. März 2022, 17:51
von __deets__
Was muss denn da umschifft werden, und was ist das Ziel?

Re: PyQt5 Signale und Slots

Verfasst: Mittwoch 30. März 2022, 17:39
von Welpe
Ich möchte dem Thread Argumente / Parameter beim aufrufen mitgeben.
Ich habe eine GUI um mit pytube Videos zu laden. Wenn ich den Download in den Thread auslagere, muß ich ihm ja auch die URL, den gewählten Stream, das gewählte Downloadverzeichnis usw. mitgeben. Das ist mein Ziel. Gefunden habe ich allerdings nur Anleitungen, in denen Argumente / Signale vom Thread kommen aber keine, wo welche zum Thread hingehen.

Re: PyQt5 Signale und Slots

Verfasst: Mittwoch 30. März 2022, 17:47
von __deets__
Haeh? Du schickst doch ein signal zum Thread. Du hast das Signal "started" mit Slot "run" verbunden. started hat keine Argumente, und run auch nicht. Also wird run im Background Thread abgearbeitet.

Wenn du das anders willst, dann definier doch ein Slot "download_youtube_video(self, url, path)", und sende dem ein Signal, dass eben diese 2 Argumente nutzt. Die Verbindung started/run darf dann natuerlich nicht mehr existieren. Denn started kommt ja automatisch.

Nachtrag: das Signal hat natürlich nur 2 Argumente. Aber der Slot braucht das Self.

Re: PyQt5 Signale und Slots

Verfasst: Freitag 1. April 2022, 05:58
von Welpe
Ok, das probiere ich aus. Danke für deine Geduld.