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

PyQt5, QObject, Qt, moveToThread, QThread

На основе одного из вопросов на форуме я написал пример по использованию 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

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

Комментарии

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

Здравствуйте, уважаемые пользователи EVILEG !!!

Если сайт вам помог, то поддержите разработку сайта финансово, пожалуйста.

Вы можете сделать это следующими способами:

Спасибо, Евгений Легоцкой

M
26 февраля 2020 г. 21:48
Metalhaker

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

  • Результат:60баллов,
  • Очки рейтинга-1
a
25 февраля 2020 г. 5:40
ayb

C++ - Тест 005. Структуры и Классы

  • Результат:83баллов,
  • Очки рейтинга4
ДЗ
24 февраля 2020 г. 14:47
Дмитрий Злобин

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

  • Результат:40баллов,
  • Очки рейтинга-8
Последние комментарии
24 февраля 2020 г. 3:54
Евгений Легоцкой

Добрый день. Там будет url, на который указывает ссылка тега a в пагинаторе, если правильно помню )) Написал этот код и забыл.
B
24 февраля 2020 г. 0:37
BahaMeirman

Евгений Здравствуйте! Не могу понять вот эту часть кода: url: jQuery(this).attr('action') наверное здесь должен быть путь к url, тогда 'action' на какой url указывает?
17 февраля 2020 г. 3:22
Евгений Легоцкой

Добрый день. Это кастомный тег, помещается в файл, который находится в каталоге templatetags myapp/ templatetags/ myapp.py
B
16 февраля 2020 г. 13:36
BahaMeirman

Добрый вечер! Монжно по подробней о теге get_companion? ссылка не работает.
Сейчас обсуждают на форуме
28 февраля 2020 г. 9:08
Pavel.K

Нужно реализовать драг N дроп из одной части экрана в другую (из 1й listview в другую) Я думаю реализовать это с помощью копирования текущего (выбранного) delegate , кто-нибудь сталкив…
m
27 февраля 2020 г. 14:12
mihenze

Попробовал у себя дома. Все работает. Вот прикладлываю проект. использовал QT5.14, MinGW5.3.0, но объединение работало и на более ранних версиях TestWowdTable.rar
27 февраля 2020 г. 10:43
mkdir Некрасов

Блаодарю! Вы очень помогли
v
27 февраля 2020 г. 9:00
vlaaad20

Проблема решилась путем следующих манипуляций: 1. Небольшое изменение url (на https://identitysso-cert.betfair.com/api/certlogin) 2. Установки OpenSSL 32-bit (разрядность должна соотве…
ДА
27 февраля 2020 г. 2:39
Денис Аргер

Добрый день!Стоит задача вносить изменения в заранее подготовленый файл. Места изменений заранее известны. Подскажите, пожалуйста.
EVILEG
О нас
Услуги
© EVILEG 2015-2019
Рекомендует хостинг TIMEWEB