Евгений Легоцкой25 ноября 2019 г. 16:53

PyQt5 - Урок 009. Использование QThread с применением moveToThread

На основе одного из вопросов на форуме я написал пример использования QThread в PyQt5, а также использования метода moveToThread для перемещения объекта класса унаследованного QObject в другой поток.

В этом примере выполняется некий алгоритм, который через сигнал возвращает текст, а также цвет текста в основной графический интерфейс. Эти данные добавляются в QTextBrowser с настройкой цвета.

Программа будет выглядеть следующим образом

Содержание

Вступление

Существует два основных подхода к использованию QThread в Qt :

  1. Создайте новый класс, наследуемый от QThread , и переопределите метод запуска.
  2. Создайте новый класс, наследуемый от QObject , напишите метод run, который будет выполнять некоторый код, и передайте экземпляр этого класса в другой поток с помощью метода moveToThread .

Первый метод рекомендуется использовать только в том случае, если вам действительно нужно переопределить класс потока, чтобы создать специальный функционал в классе потока. Если вам нужно выполнить какой-то код в другом потоке, то для этого нужно создать отдельный класс, который будет передан в другой поток с помощью метода moveToThread .

Также сразу хочу отметить, что нельзя передавать объекты GUI в другие потоки. Программы Qt имеют два типа потоков:

  • Основной поток. Графический поток
  • Рабочие процессы. Рабочие потоки

Все объекты графического интерфейса должны создаваться и работать только в потоке графического интерфейса, тогда как различные другие алгоритмы могут выполняться в рабочих потоках.

цитирую документацию

Как уже упоминалось, каждая программа имеет один поток при запуске. Этот поток называется «основной поток» (также известный как «поток GUI» в приложениях Qt). Графический интерфейс Qt должен работать в этом потоке. Все виджеты и несколько связанных с ними классов, например QPixmap, не работают во вторичных потоках. Вторичный поток обычно называют «рабочим потоком», поскольку он используется для выгрузки обработки из основного потока.

То есть если, даже если у вас что-то и сработает в другом потоке, то это будет лишь случайность, которая рано или поздно даст о себе знать. И ваша программа перестанет работать. Никогда не передавайте другие объекты GUI другим потокам.

Программа

Теперь рассмотрим код нашей программы. Обратите внимание, что алгоритм действий будет следующим

  1. Пишем класс, который наследуется от QObject и имеет метод run для выполнения кода в другом потоке
  2. В конструкторе окна создайте объект потока
  3. В конструкторе окна создать объект, который будет передан другому потоку
  4. Перенести объект в другой поток
  5. Соединяем сигналы и слоты
  6. Запустите нить

Всю инициализацию объекта и потока желательно выполнять в такой последовательности, если у вас еще нет достаточного опыта.
Однако тогда вы бы не читали эту статью.
Если поменять местами шаги 4 и 5, то вы сначала соедините сигналы и слоты, а потом перенесете объект в другой поток, что разорвет связь сигнал/слот. Окно приложения перестанет работать. Или приложение может просто вылететь.

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
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
- блог компании
Поддержать автора Donate
c

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

b
  • #
  • 6 мая 2020 г. 15:27
  • (ред.)

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

b

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

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

b

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

YA

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

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

День добрый

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

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

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

b

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

O

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

Вы можете использовать переменную 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

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

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

O

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

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Card image cap
Pulsum Via

Проект для путешественников от EVILEG.

Перейти
Timeweb

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг
Поделиться в социальных сетях
Donate

Проект EVILEG перешёл на некоммерческую основу и будет развиваться исключительно на энтузиазме создателя сайта, энтузиазме пользователей, пожертвованиях и реферальной системе хостинга

Спасибо за вашу поддержку

Доступные способы поддержки проекта

PayPal

PatreonYooMoneyПодробнее
ДП

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:10баллов,
  • Очки рейтинга-10
ВС

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:0баллов,
  • Очки рейтинга-10
ВС

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50баллов,
  • Очки рейтинга-4
Последние комментарии
Ds

Android и QML - Добавление Splash Screen

Интересен формат иконки, если это png, то как решается проблема scalability? не растягивается ли лого на китайфонах с 1280х2500? У меня просто сплеш скрин с градиентом и логотипом, и вот несколь…
p

Qt/C++ - Урок 023. Перетаскивание QGraphicsItem на QGraphicsScene мышью

FIGURE Abdominopelvic regions. Zjuaqd https://newfasttadalafil.com/ - Cialis Cialis Recommendations for preparing children and adolescents for invasive cardiac procedures a statement…
КГ

Как использовать вложенные формы в Django

Спасибо за полезную статью. Подскажите пожалуйста, что делать если нужно реализовать большее количество вложенных форм? Например если на модель Address ссылается fk другой модели, на котору…

Qt/C++ - Урок 051. QMediaPlayer - Аудио плеер на Qt

Не думаю, QMediaPlayer в один поток проигрывает. Если вам нужно одновременное воспроизведение нескольких аудиоисточников, то вам нужна Bass audio library , насколько знаю, её обычно и…
АГ

Qt/C++ - Урок 051. QMediaPlayer - Аудио плеер на Qt

есть такая вообще возможность ?
Сейчас обсуждают на форуме
АБ

Sorting the added QML elements in the ListModel

I am writing an alarm clock in QML, I am required to sort the alarms in ascending order (depending on the date or time (if there are several alarms on the same day). I've done the sorting …

Изменение поведения QGroupBox при клике на его чекбокс

Я вынес виджеты вынес за пределы QGroupBox в итоге.

QSqlRelatipnalTabelModel Qt 4.8.1 как получить id внешней связи?

Есть еще принципиально другой вариант решить раз и навсегда вопрос с полей id внешней связи. Это форкнуть Qt 4.8.1 QSqlTableModel, то есть создать свою ветку развития. Например создадим кл…

Добавление AndroidManifest.xml в cmake

Добрый день. Как добавить AndroidManifest.xml в cmake? Это не работвет set(ANDROID_PACKAGE_SOURCE_DIR ${PROJECT_SOURCE_DIR}/android CACHE INTERNAL "")set(ANDROID_BUID_DIR ${CMAKE_C…
s

Событие wheelEvent для виджета QLineEdit

вот что получилось: gui.py from PyQt5 import QtCore, QtGui, QtWidgets class LineEdit(QtWidgets.QLineEdit): def wheelEvent(self, event): #print("_") delta = 1 if e…
О нас
Услуги
© EVILEG 2015-2022
Рекомендует хостинг TIMEWEB