Множественное наследование QObject и QThread вместе

Вопрос или проблема

Я пытаюсь создать набор конечных автоматов для обработки различных задач в моем проекте на PyQt. В процессе обработки графики на одном потоке я создал два типа конечных автоматов: StateMachine, который должен наследовать QObject, и ThreadedStateMachine, который, чтобы избежать дублирования кода, я заставил наследоваться и от StateMachine, и от QThread.

Вот мой минимально полный код для воспроизведения проблемы:

from PyQt5.QtCore import QObject, QThread


class StateMachine(QObject):
    def __init__(self):
        super().__init__()


class ThreadedStateMachine(StateMachine, QThread):
    def __init__(self):
        super().__init__()


if __name__ == "__main__":
    t = ThreadedStateMachine()

Я ожидал, что это сработает без проблем, но вместо этого я получаю следующее исключение:

QObject::connect: No such signal ThreadedStateMachine::started()
Traceback (most recent call last):
  File "/snap/pycharm-community/132/helpers/pydev/pydevd.py", line 1758, in <module>
    main()
  File "/snap/pycharm-community/132/helpers/pydev/pydevd.py", line 1752, in main
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/snap/pycharm-community/132/helpers/pydev/pydevd.py", line 1147, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/snap/pycharm-community/132/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/home/aaron/.PyCharmCE2019.1/config/scratches/emerson_pmd/new/scratch_7.py", line 15, in <module>
    t = ThreadedStateMachine()
  File "/home/aaron/.PyCharmCE2019.1/config/scratches/emerson_pmd/new/scratch_7.py", line 11, in __init__
    super().__init__()
  File "/home/aaron/.PyCharmCE2019.1/config/scratches/emerson_pmd/new/scratch_7.py", line 6, in __init__
    super().__init__()
  File "/snap/pycharm-community/132/helpers/pydev/_pydev_bundle/pydev_monkey_qt.py", line 181, in __init__
    self.started = StartedSignalWrapper(self, self.started)
  File "/snap/pycharm-community/132/helpers/pydev/_pydev_bundle/pydev_monkey_qt.py", line 151, in __init__
    self.original_started.connect(self._signal)
TypeError: connect() failed between started() and _signal()

Какой правильный способ комбинировать классы QObject и QThread в одном объекте?

Примечание

В настоящее время я использую threading.Thread как обходное решение, но я хотел бы иметь возможность запускать QMessageBox из другого потока, что вызывает ошибку:

QObject::connect: Cannot queue arguments of type 'QItemSelection'
(Make sure 'QItemSelection' is registered using qRegisterMetaType().)
QBasicTimer::start: Timers cannot be started from another thread

Я считаю, что использование QThread будет работать в этом случае.

У вас есть проблема. PyQt не позволяет двойное наследование двух QObject. StateMachine является QObject, а QThread также является QObject (см. документацию).

Я не думаю, что проблема вызвана threading.Thread(), я подозреваю, что это вызвано тем, что вы пытаетесь изменить какой-то объект, который живет в потоке и не является безопасным для потоков. QObjects не являются безопасными для потоков, поэтому вы должны взаимодействовать с этими объектами в потоке, где они живут, а модели являются QObject.

Читайте:

Ответ или решение

Сложность, с которой вы столкнулись в своем проекте PyQt, обусловлена двойным наследованием от классов QObject и QThread. Qt, и соответственно PyQt, не поддерживает множественное наследование для классов, производных от QObject, поскольку это может привести к неопределённому поведению и проблемам с сигналами и слотами.

Проблема двойного наследования

Класс QThread уже является наследником QObject. Когда вы пытаетесь создать класс ThreadedStateMachine, который наследуется от StateMachine (который тоже является QObject) и QThread, возникает проблема в сигналах и слотах, поскольку Qt не может корректно обрабатывать множественные базовые классы QObject. Поэтому вы получаете ошибку QObject::connect: No such signal ThreadedStateMachine::started(), что указывает на недоступность сигнала started для вашего класса.

Решение проблем

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

  1. Составной подход: Вместо того чтобы наследовать от обоих классов, вы можете сделать ThreadedStateMachine отдельным классом, который имеет экземпляр QThread и экземпляр StateMachine. Вот пример такого подхода:
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot

class StateMachine(QObject):
    def __init__(self):
        super().__init__()

    # Здесь добавьте методы и сигналы вашего StateMachine

class Worker(QObject):
    finished = pyqtSignal()

    def run(self):
        # Здесь выполняйте вашу долгую задачу
        print("Выполнение задачи...")
        self.finished.emit()

class ThreadedStateMachine(QObject):
    def __init__(self):
        super().__init__()
        self.thread = QThread()
        self.worker = Worker()

        # Перемещение worker в новый поток
        self.worker.moveToThread(self.thread)

        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)

    def start(self):
        self.thread.start()

if __name__ == "__main__":
    t = ThreadedStateMachine()
    t.start()

В этом примере класс Worker является объектом, который выполняет длительную задачу в отдельном потоке, а ThreadedStateMachine контролирует эту работу.

  1. Использование сигналов и слотов: Не забывайте о механизме сигналов и слотов, чтобы безопасно передавать данные между потоками. Это особенно важно при взаимодействии с графическим интерфейсом (GUI), чтобы избежать проблем с многопоточностью.

  2. Взаимодействие с GUI: Если вам нужно взаимодействовать с элементами GUI из другого потока, используйте сигналы для передачи данных обратно в основной поток. Например, если Worker хочет отправить сообщение обратно в GUI, он может вызвать сигнал, который следует соединить со слотом в ThreadedStateMachine, занимающимся обновлением интерфейса.

Заключение

Ваша задача по созданию многопоточного состояния машины требует внимательного подхода к архитектуре приложения. Избегайте двойного наследования от QObject и используйте составной подход с помощью взаимодействия через сигналы и слоты для обеспечения корректной работы вашего приложения. Эти методы не только устранят текущие ошибки, но и помогут создать более устойчивую и понятную архитектуру.

Оцените материал
Добавить комментарий

Капча загружается...