Evgenii Legotckoi
Evgenii Legotckoi25. November 2019 17:53

PyQt5 - Tutorial 009. QThread mit MoveToThread verwenden

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 :

  1. Erstellen Sie eine neue Klasse, die von QThread erbt, und überschreiben Sie die Startmethode.
  2. 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.

Dokumentation zitieren

> 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

  1. Wir schreiben eine Klasse, die von QObject erbt und eine run-Methode zum Ausführen von Code in einem anderen Thread hat
  2. Erstellen Sie im Fensterkonstruktor ein Thread-Objekt
  3. Erstellen Sie im Fensterkonstruktor ein Objekt, das an einen anderen Thread übergeben wird
  4. Übertragen Sie das Objekt in einen anderen Thread
  5. Verbinden Sie Signale und Slots
  6. 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())

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

c
  • 27. November 2019 08:44

Огромное спасибо!

b
  • 6. Mai 2020 16:27
  • (bearbeitet)

простите за беспокойсто. Разобрался )) Спасибо Вам огромное. По сути у Вас тольько и разобрался с сигналами и слотами

b
  • 6. Mai 2020 17:16

в продолжение, хотелось бы уточнить такой вопрос. Испускаемые сигналы - они глобальны? то есть на сгенерированный pyqtSignal в классе А, можно ли "подписаться" в классах B,C,D своими слотами? То есть по одному сигналу, может ли каждый класс выполнять что-то свое?

Evgenii Legotckoi
  • 6. Mai 2020 17:18

Да, можно. к одному сигналу можно быть подключено какое угодно количество слотов в каком угодно наборе объектов.

b
  • 7. Mai 2020 03:40

спасибо Вам большое

YA
  • 16. April 2021 07:25

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?

import sys
import time
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QPushButton, QListWidget, QVBoxLayout, QLineEdit
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot, QObject
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from pymodbus.transaction import ModbusRtuFramer

class RequestHandler(QObject):
    running = False
    output_signal = pyqtSignal(list)

    @pyqtSlot(str, int)
    def run(self, ip, port, unit):
        client = ModbusClient(ip, port=port, framer=ModbusRtuFramer)
        client.connect()
        print("run")
        while True:
            print(int(QThread.currentThreadId()))

            for i in range(2):
                try:
                    m = client.read_holding_registers(0,9, unit = unit[i])
                    self.output_signal.emit(m.registers)
                except:
                    self.output_signal.emit(["ERROR"])

            QThread.msleep(2000)

class MyWindow(QtWidgets.QWidget):

    def __init__(self, parent=None):
        super().__init__()
        self.list1 = QLineEdit()
        self.buton = QPushButton("BASlAT")
        self.v = QVBoxLayout()
        self.v.addWidget(self.list1)
        self.v.addWidget(self.buton)
        self.buton.clicked.connect(self.start)
        self.setLayout(self.v)
        self.show()

    @pyqtSlot(list)
    def addNewTextAndColor(self, output):
        print(output)

        pass

    def start(self):
        unit = [14, 15]
        self.thread = QThread()
        self.browserHandler = RequestHandler()
        self.browserHandler.moveToThread(self.thread)
        print(int(self.thread.currentThreadId()))
        self.browserHandler.output_signal.connect(self.addNewTextAndColor)
        self.thread.started.connect(self.browserHandler.run("192.168.1.100", 502, unit))
        self.thread.start()


app = QtWidgets.QApplication(sys.argv)
window = MyWindow()

sys.exit(app.exec())

c
  • 21. August 2021 16:38

Здравствуйте.
Разрешите пару вопросов...
1. Зачем нужен running = False ?
2. Можно ли (нужно?) принудительно завершать поток?
Ещё раз спасибо огромное! Ваш ресурс пожалуй лучший по Qt и PyQt на русском (и не только) языке!

Evgenii Legotckoi
  • 11. Oktober 2021 01:53

День добрый

  1. Не нужен, случайно осталось при подготовке материала
  2. Можно, нужно, не обязательно - зависит от логики вашей программы
b
  • 11. Oktober 2021 03:44

А можете, пожалуйста, уточнить каким образом можно принудительно завершить поток?

Evgenii Legotckoi
  • 11. Oktober 2021 03:48

Вызвать либо метод quit() либо эквивалентный его вариант - метод exit(0)

b
  • 11. Oktober 2021 03:57

Спасибо большое

O
  • 16. Mai 2022 06:52

Не уверен, что кто-то ответил спустя столько времени, но все же. Возможно кто-то отправлять сигнал из gui во второй поток, активируя там функцию run повторно? На примере чата. На каждон отправленое сообщение из gui активировать по новой функцию Run(), в которой бекенд обработки сообщений. Просто каждый раз завершать поток и стартовать его заного - очень долго. Как использовать поток повторно, после завершения метода run?

Evgenii Legotckoi
  • 16. Mai 2022 07:28

Вы можете использовать переменную running, которой можете контролировать выполнение функции run

class BrowserHandler(QtCore.QObject):
    running = False
    newTextAndColor = QtCore.pyqtSignal(str, object)

    # method which will execute algorithm in another thread
    def run(self):
        while running:
            pass

Главное, это правильно обработать установку переменной running в рамках вашей программы

O
  • 16. Mai 2022 07:49

Сначала так и использовал, но в случае установки флага running в состояние выхода из цикла, run() завершается, поток все еще живет, но заново запустить run, обращаясь к этому методу так же, как и при старте потока, уже не могу. Возможно я как-то не так это делаю ._.

Evgenii Legotckoi
  • 16. Mai 2022 07:59

Да, вы правы. Я не подумал об этом.
В этом случае я бы попытался написать программу по другому.
Например, добавить в BrowserHandler очередь сообщений, а метод run не завершать, а заставить его обрабатывать сообщения каждый раз, когда что-то добавляется в очередь сообщений. Это будет наиболее правильное решение.

O
  • 16. Mai 2022 11:33

Решение хорошее, сейчас так и делаю. Но все равно остается открытым вопрос подвязки ивента из вне. Проще говоря, не хочется гонять вечный цикл в run, постоянго проверяя изменения очереди (пусть даже поставим QThread.msleep(100) на каждый виток цикла). А как заставить run шевелиться только по отправке сообщения,

Evgenii Legotckoi
  • 17. Mai 2022 03:48

Попробуйте принудительно вызывать сигнал started у потока. Это является потокобезопасным. И в данном случае вызов сигнала started должно запустить выполнения метода run, а потом продолжить выполнение главного потока.

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
A
ALO1ZE19. Oktober 2024 08:19
Fb3-Dateileser auf Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken