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

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

Содержание

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

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

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

Введение

Существуют два основных подхода для использования 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
  • #
  • 6 мая 2020 г. 16: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())

Комментарии

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

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

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

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

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

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

  • Результат:80баллов,
  • Очки рейтинга4
ДЦ

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

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

Qt - Тест 001. Сигналы и слоты

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

Как соответствовать новым требованиям Google Play

Ку всем! Вопрос! Как с помощью JNI вызвать оплату на android устройстве? Попробовал Qt Purchasing вроде пошло... но android пишет что библиотека устарела. В qt6 пока поддержки purchasing нет. Р…

Django - Урок 014. Вывод списка популярных статей на Django

Я как понял, этот метод создает статистику каждый день (на каждый день), не удаляя старые данные за день и выводит все данные за сегодняшние просмотры у всех статей?

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

Изменить запрос при создании модели.
L
  • Livis
  • 30 мая 2021 г. 13:18

Django - Урок 001. Развёртывание сайта на Django + PostgreSQL + Gunicorn + Nginx

Отвечал на все команды после sudo service supervisor start Исправил таким образом sudo killall supervisordsudo supervisord -c /etc/supervisor/supervisord.conf
D

Django - Урок 001. Развёртывание сайта на Django + PostgreSQL + Gunicorn + Nginx

Это на какую команду так отвечает?
Сейчас обсуждают на форуме
v
  • vika
  • 17 июня 2021 г. 0:19

Вопрос новичка ui-форма

все получилось. создала обьект, исправила ошибки и все заработало. спасибо, что есть с кем поговорить.
s

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

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

Django как воспользоваться результатом метода?

Priv = 2020 Now = 2021 Next = 2022 Rok = ( (None, "Nie wybran"), (Priv, '2020'), (Now, '2021'), (Next, '2022') ) Rok = models.IntegerField(choices=Rok, de…

QScrollArea dynamically add QCheckBoxes

Всё правильно. Это просто спамер, который отправился в вечный бан.

Папка с шаблоном сайта

Есть проект с поддержкой динамического создания поддоменов. Как назначить разные пути к шаблонам в templates from django.shortcuts import renderfrom horticulture.utilities import get_horticu…
О нас
Услуги
© EVILEG 2015-2021
Рекомендует хостинг TIMEWEB