- 1. Einführung
- 2. Programm
Basierend auf einer der Fragen im Forum habe ich ein Beispiel für die Verwendung von QThread in PyQt5 sowie die Verwendung der moveToThread-Methode geschrieben, um ein Objekt einer von QObject geerbten Klasse in einen anderen Thread zu verschieben.
In diesem Beispiel wird ein Algorithmus ausgeführt, der durch ein Signal den Text sowie die Farbe des Textes an die Haupt-GUI zurückgibt. Diese Daten werden dem QTextBrowser mit einer Farbeinstellung hinzugefügt.
Das Programm wird so aussehen
Einführung
Es gibt zwei Hauptansätze zur Verwendung von QThread in Qt :
- Erstellen Sie eine neue Klasse, die von QThread erbt, und überschreiben Sie die Startmethode.
- Erstellen Sie eine neue Klasse, die von QObject erbt, schreiben Sie eine run-Methode, die Code ausführt, und übergeben Sie eine Instanz dieser Klasse mithilfe der moveToThread -Methode an einen anderen Thread.
Die erste Methode wird nur empfohlen, wenn Sie die Thread-Klasse wirklich überschreiben müssen, um spezielle Funktionen in der Thread-Klasse zu erstellen. Wenn Sie Code in einem anderen Thread ausführen müssen, müssen Sie dafür eine separate Klasse erstellen, die mit der Methode moveToThread an einen anderen Thread übertragen wird.
Ich möchte auch gleich darauf hinweisen, dass Sie GUI-Objekte nicht an andere Threads übergeben können. Qt-Programme haben zwei Arten von Threads:
- Hauptstrom. Grafik-Stream
- Arbeitsabläufe. Arbeitsabläufe
Alle GUI-Objekte müssen nur auf dem GUI-Thread erstellt und ausgeführt werden, während verschiedene andere Algorithmen auf Worker-Threads ausgeführt werden können.
> Wie bereits erwähnt, hat jedes Programm beim Start einen Thread. Dieser Thread wird als "Haupt-Thread" (in Qt-Anwendungen auch als "GUI-Thread" bezeichnet) bezeichnet. Die Qt-GUI muss auf diesem Thread ausgeführt werden. Alle Widgets und einige verwandte Klassen wie QPixmap werden nicht auf sekundären Threads ausgeführt. Der sekundäre Thread wird allgemein als "Worker-Thread" bezeichnet, da er verwendet wird, um die Verarbeitung vom Haupt-Thread abzuladen.
Das heißt, wenn, selbst wenn etwas in einem anderen Thread für Sie funktioniert, dann ist es nur ein Zufall, der sich früher oder später bemerkbar macht. Und Ihr Programm wird nicht mehr funktionieren. Übergeben Sie niemals andere GUI-Objekte an andere Threads.
Programm
Schauen wir uns nun unseren Programmcode an. Bitte beachten Sie, dass der Aktionsalgorithmus wie folgt sein wird
- Wir schreiben eine Klasse, die von QObject erbt und eine run-Methode zum Ausführen von Code in einem anderen Thread hat
- Erstellen Sie im Fensterkonstruktor ein Thread-Objekt
- Erstellen Sie im Fensterkonstruktor ein Objekt, das an einen anderen Thread übergeben wird
- Übertragen Sie das Objekt in einen anderen Thread
- Verbinden Sie Signale und Slots
- Führen Sie einen Thread aus
Es ist wünschenswert, alle Objekt- und Thread-Initialisierungen in dieser Reihenfolge durchzuführen, wenn Sie nicht bereits über ausreichende Erfahrung verfügen.
Dann würden Sie diesen Artikel jedoch nicht lesen.
Wenn Sie die Schritte 4 und 5 vertauschen, verbinden Sie zuerst Signale und Slots und übertragen das Objekt dann auf einen anderen Thread, wodurch die Signal/Slot-Verbindung unterbrochen wird. Das Anwendungsfenster funktioniert nicht mehr. Oder die App stürzt einfach ab.
import sys import time from PyQt5 import QtCore, QtWidgets from PyQt5.QtGui import QColor class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(453, 408) self.verticalLayout = QtWidgets.QVBoxLayout(Form) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setObjectName("verticalLayout_2") self.textBrowser = QtWidgets.QTextBrowser(Form) self.textBrowser.setObjectName("textBrowser") self.verticalLayout_2.addWidget(self.textBrowser) self.verticalLayout.addLayout(self.verticalLayout_2) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.pushButton = QtWidgets.QPushButton(Form) self.pushButton.setObjectName("pushButton") self.horizontalLayout.addWidget(self.pushButton) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.verticalLayout.addLayout(self.horizontalLayout) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Example")) self.pushButton.setText(_translate("Form", "Input")) # Object, which will be moved to another thread class BrowserHandler(QtCore.QObject): running = False newTextAndColor = QtCore.pyqtSignal(str, object) # method which will execute algorithm in another thread def run(self): while True: # send signal with new text and color from aonther thread self.newTextAndColor.emit( '{} - thread 2 variant 1.\n'.format(str(time.strftime("%Y-%m-%d-%H.%M.%S", time.localtime()))), QColor(0, 0, 255) ) QtCore.QThread.msleep(1000) # send signal with new text and color from aonther thread self.newTextAndColor.emit( '{} - thread 2 variant 2.\n'.format(str(time.strftime("%Y-%m-%d-%H.%M.%S", time.localtime()))), QColor(255, 0, 0) ) QtCore.QThread.msleep(1000) class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None): super().__init__() self.ui = Ui_Form() self.ui.setupUi(self) # use button to invoke slot with another text and color self.ui.pushButton.clicked.connect(self.addAnotherTextAndColor) # create thread self.thread = QtCore.QThread() # create object which will be moved to another thread self.browserHandler = BrowserHandler() # move object to another thread self.browserHandler.moveToThread(self.thread) # after that, we can connect signals from this object to slot in GUI thread self.browserHandler.newTextAndColor.connect(self.addNewTextAndColor) # connect started signal to run method of object in another thread self.thread.started.connect(self.browserHandler.run) # start thread self.thread.start() @QtCore.pyqtSlot(str, object) def addNewTextAndColor(self, string, color): self.ui.textBrowser.setTextColor(color) self.ui.textBrowser.append(string) def addAnotherTextAndColor(self): self.ui.textBrowser.setTextColor(QColor(0, 255, 0)) self.ui.textBrowser.append('{} - thread 2 variant 3.\n'.format(str(time.strftime("%Y-%m-%d-%H.%M.%S", time.localtime())))) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec())
Огромное спасибо!
простите за беспокойсто. Разобрался )) Спасибо Вам огромное. По сути у Вас тольько и разобрался с сигналами и слотами
в продолжение, хотелось бы уточнить такой вопрос. Испускаемые сигналы - они глобальны? то есть на сгенерированный pyqtSignal в классе А, можно ли "подписаться" в классах B,C,D своими слотами? То есть по одному сигналу, может ли каждый класс выполнять что-то свое?
Да, можно. к одному сигналу можно быть подключено какое угодно количество слотов в каком угодно наборе объектов.
спасибо Вам большое
Hello. Let's say I want to send some variables to "run" define. How can we do that? I modified your code, I tried something like below, but the GUI is frozen that way. I could not be able to understand it. Can you please give me some advice?
Здравствуйте.
Разрешите пару вопросов...
1. Зачем нужен running = False ?
2. Можно ли (нужно?) принудительно завершать поток?
Ещё раз спасибо огромное! Ваш ресурс пожалуй лучший по Qt и PyQt на русском (и не только) языке!
День добрый
А можете, пожалуйста, уточнить каким образом можно принудительно завершить поток?
Вызвать либо метод quit() либо эквивалентный его вариант - метод exit(0)
Спасибо большое
Не уверен, что кто-то ответил спустя столько времени, но все же. Возможно кто-то отправлять сигнал из gui во второй поток, активируя там функцию run повторно? На примере чата. На каждон отправленое сообщение из gui активировать по новой функцию Run(), в которой бекенд обработки сообщений. Просто каждый раз завершать поток и стартовать его заного - очень долго. Как использовать поток повторно, после завершения метода run?
Вы можете использовать переменную running, которой можете контролировать выполнение функции run
Главное, это правильно обработать установку переменной running в рамках вашей программы
Сначала так и использовал, но в случае установки флага running в состояние выхода из цикла, run() завершается, поток все еще живет, но заново запустить run, обращаясь к этому методу так же, как и при старте потока, уже не могу. Возможно я как-то не так это делаю ._.
Да, вы правы. Я не подумал об этом.
В этом случае я бы попытался написать программу по другому.
Например, добавить в BrowserHandler очередь сообщений, а метод run не завершать, а заставить его обрабатывать сообщения каждый раз, когда что-то добавляется в очередь сообщений. Это будет наиболее правильное решение.
Решение хорошее, сейчас так и делаю. Но все равно остается открытым вопрос подвязки ивента из вне. Проще говоря, не хочется гонять вечный цикл в run, постоянго проверяя изменения очереди (пусть даже поставим QThread.msleep(100) на каждый виток цикла). А как заставить run шевелиться только по отправке сообщения,
Попробуйте принудительно вызывать сигнал started у потока. Это является потокобезопасным. И в данном случае вызов сигнала started должно запустить выполнения метода run, а потом продолжить выполнение главного потока.