Вопрос или проблема
Я пытаюсь создать набор конечных автоматов для обработки различных задач в моем проекте на 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
для вашего класса.
Решение проблем
Чтобы избежать этой ошибки и эффективно использовать как состояние машины, так и потоки, вам необходимо изменить архитектуру вашего кода. Вот несколько рекомендуемых подходов:
- Составной подход: Вместо того чтобы наследовать от обоих классов, вы можете сделать
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
контролирует эту работу.
-
Использование сигналов и слотов: Не забывайте о механизме сигналов и слотов, чтобы безопасно передавать данные между потоками. Это особенно важно при взаимодействии с графическим интерфейсом (GUI), чтобы избежать проблем с многопоточностью.
-
Взаимодействие с GUI: Если вам нужно взаимодействовать с элементами GUI из другого потока, используйте сигналы для передачи данных обратно в основной поток. Например, если
Worker
хочет отправить сообщение обратно в GUI, он может вызвать сигнал, который следует соединить со слотом вThreadedStateMachine
, занимающимся обновлением интерфейса.
Заключение
Ваша задача по созданию многопоточного состояния машины требует внимательного подхода к архитектуре приложения. Избегайте двойного наследования от QObject и используйте составной подход с помощью взаимодействия через сигналы и слоты для обеспечения корректной работы вашего приложения. Эти методы не только устранят текущие ошибки, но и помогут создать более устойчивую и понятную архитектуру.