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

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

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

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

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

Содержание

ADS

Введение

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

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

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

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

  • Главный поток. GUI thread
  • Рабочие потоки. Worker threads

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

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

As mentioned, each program has one thread when it is started. This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads. A secondary thread is commonly referred to as a "worker thread" because it is used to offload processing work from the main thread.

То есть если, даже у вас что-то и заработает в другом потоке, то это будет лишь случайность, которая рано или поздно даст о себе знать. А ваша программа перестанет работать. Никогда не передавайте другие 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"))


# Объект, который будет перенесён в другой поток для выполнения кода
class BrowserHandler(QtCore.QObject):
    running = False
    newTextAndColor = QtCore.pyqtSignal(str, object)

    # метод, который будет выполнять алгоритм в другом потоке
    def run(self):
        while True:
            # посылаем сигнал из второго потока в GUI поток
            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)

            # посылаем сигнал из второго потока в GUI поток
            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)
        # используем кнопку для добавления текста с цветом в главном потоке
        self.ui.pushButton.clicked.connect(self.addAnotherTextAndColor)

        # создадим поток
        self.thread = QtCore.QThread()
        # создадим объект для выполнения кода в другом потоке
        self.browserHandler = BrowserHandler()
        # перенесём объект в другой поток
        self.browserHandler.moveToThread(self.thread)
        # после чего подключим все сигналы и слоты
        self.browserHandler.newTextAndColor.connect(self.addNewTextAndColor)
        # подключим сигнал старта потока к методу run у объекта, который должен выполнять код в другом потоке
        self.thread.started.connect(self.browserHandler.run)
        # запустим поток
        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

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

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

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

Комментарии

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

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

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

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

Посмотреть Хостинг
k

C++ - Тест 002. Константы

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

C++ - Тест 001. Первая программа и типы данных

  • Результат:40баллов,
  • Очки рейтинга-8
k

C++ - Тест 003. Условия и циклы

  • Результат:64баллов,
  • Очки рейтинга-1
Популярные публикации за последние 90 дней
Последние комментарии
R

Установка драйвера QIBASE (Firebird) Qt 6.2.1 на openSUSE Tumbleweed

Just that is necessary. A good theme, I will participate. Together we can come to a right answer. https://gay0day.com
C

Установка драйвера QIBASE (Firebird) Qt 6.2.1 на openSUSE Tumbleweed

ротационные прокси
Y
  • Yar
  • 15 ноября 2021 г. 2:33

QML - Урок 004. Сигналы и слоты в Qt QML

У связывания интерейса прогрммы с ядром через контекст (context->setContextProperty("appCore", &appCore);) есть один существенный недостаток, упоминание о котором я нигде не нашел, а выяв…
v
  • v
  • 9 ноября 2021 г. 22:07

Сборка драйвера QMYSQL (MariaDB) Windows 10 x64 QT 5.13.0 Mingw73_64

Спасибо, добрый человек! Перелопатил весь stackoverflow в поисках ответа почему MySql....no , не осознавая что mingw32-make clean не очищает то что надо. После удаления вышеуказаных…

Django - Урок 045. Перемещение моделей из одного приложения в другое

у меня была проблема что у меня в кубере автоматом миграция запускалась сделал так (как вариант решения, добовлял каждой миграции RunSQL): operations = [ migrations.RunSQL(''' …
Сейчас обсуждают на форуме
s

Ключевое слово class

Разобрался,на стаковерфлоу нашел топик и понял почему так происходило .
ИП

For each

For each разработан Microsoft для своего компилятора VS, в стандарте его нет, на официальном сайте не рекомендуется его использовать
U

Qt - как скомпилировать 32bit-программу под Linux 64bit?

Неожиданно появилась необходимость скомпилировать программу для 32-разрядного Linux-а сидя на 64-разрядном... Читал-листал интернеты, мало что понял... Проверил версию: gcc -v Target:…

Авторизация в приложении.

Ничего не надо скачивать, всё должно работать "из коробки".
o
  • ost.vld
  • 22 ноября 2021 г. 0:12

Создание страницы в QML, где нужно выровнить текст по всей ширине экрана и создать скроллинг

https://doc.qt.io/qt-5/qml-qtquick-controls2-scrollview.html https://evileg.com/ru/post/186/
О нас
Услуги
© EVILEG 2015-2021
Рекомендует хостинг TIMEWEB