Как подать сигнал от запущенного QThread обратно в графический интерфейс PyQt, который его запустил?

Я пытаюсь понять, как использовать сигнализацию от Qthread обратно к запущенному интерфейсу Gui.

Настройка: у меня есть процесс (моделирование), который должен выполняться почти бесконечно (или, по крайней мере, в течение очень длительных периодов времени). Пока он выполняется, он выполняет различные вычисления, и некоторые результаты должны быть отправлены обратно в GUI, который будет отображать их соответствующим образом в режиме реального времени. Я использую PyQt для графического интерфейса. Первоначально я пытался использовать модуль потоковой обработки python, а затем переключился на QThreads после прочтения нескольких сообщений здесь, в SO, и в других местах.

Согласно этому сообщению в блоге Qt вы делаете это неправильно предпочтительным способом использования QThread является создание объекта QObject и последующее его перемещение в объект Qthread. Поэтому я последовал совету в фоновом потоке с QThread в PyQt "> этот вопрос SO и попробовал простое тестовое приложение (код ниже): оно открывает простой графический интерфейс, позволяет запустить фоновый процесс, и предполагается обновить значение шага в спинбокс.

Но это не работает. Графический интерфейс никогда не обновляется. Что я делаю неправильно?

import time, sys
from PyQt4.QtCore  import *
from PyQt4.QtGui import * 

class SimulRunner(QObject):
    'Object managing the simulation'

    stepIncreased = pyqtSignal(int, name = 'stepIncreased')
    def __init__(self):
        super(SimulRunner, self).__init__()
        self._step = 0
        self._isRunning = True
        self._maxSteps = 20

    def longRunning(self):
        while self._step  < self._maxSteps  and self._isRunning == True:
            self._step += 1
            self.stepIncreased.emit(self._step)
            time.sleep(0.1)

    def stop(self):
        self._isRunning = False

class SimulationUi(QDialog):
    'PyQt interface'

    def __init__(self):
        super(SimulationUi, self).__init__()

        self.goButton = QPushButton('Go')
        self.stopButton = QPushButton('Stop')
        self.currentStep = QSpinBox()

        self.layout = QHBoxLayout()
        self.layout.addWidget(self.goButton)
        self.layout.addWidget(self.stopButton)
        self.layout.addWidget(self.currentStep)
        self.setLayout(self.layout)

        self.simulRunner = SimulRunner()
        self.simulThread = QThread()
        self.simulRunner.moveToThread(self.simulThread)
        self.simulRunner.stepIncreased.connect(self.currentStep.setValue)


        self.connect(self.stopButton, SIGNAL('clicked()'), self.simulRunner.stop)
        self.connect(self.goButton, SIGNAL('clicked()'), self.simulThread.start)
        self.connect(self.simulRunner,SIGNAL('stepIncreased'), self.currentStep.setValue)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    simul = SimulationUi()
    simul.show()
    sys.exit(app.exec_())

person stefano    schedule 26.04.2013    source источник


Ответы (1)


Проблема здесь проста: вашему SimulRunner никогда не посылается сигнал, который заставит его начать свою работу. Один из способов сделать это — подключить его к сигналу started потока.

Кроме того, в python вы должны использовать новый способ подключения сигналов:

...
self.simulRunner = SimulRunner()
self.simulThread = QThread()
self.simulRunner.moveToThread(self.simulThread)
self.simulRunner.stepIncreased.connect(self.currentStep.setValue)
self.stopButton.clicked.connect(self.simulRunner.stop)
self.goButton.clicked.connect(self.simulThread.start)
# start the execution loop with the thread:
self.simulThread.started.connect(self.simulRunner.longRunning)
...
person mata    schedule 26.04.2013
comment
Вы абсолютно правы, я только что понял ошибку. Я вырезал и вставил код из предыдущей версии, в которой я создавал подклассы Qthread вместо использования moveToThread, и совершенно забыл о методе start(). Спасибо, что указали на это. - person stefano; 26.04.2013
comment
это очень интересный пример. Однако следуйте этому предложению, поток запускается, но кнопка остановки не останавливает его. Было бы здорово иметь рабочий пример. И, может быть, кнопка «Перейти», которая может перезапустить все это - person Fabrizio; 08.08.2013
comment
Stop просто отправляет сигнал, но поскольку simulThread занят выполнением longRunning, этот сигнал может быть обработан только после выхода из этого метода, поэтому он на самом деле не останавливает счетчик. Для этого stop нужно будет вызывать из другого потока (например, из основного потока графического интерфейса). Чтобы перезапустить счетчик, вам нужно сбросить флаг _isRunning, если он был остановлен, но это выходит за рамки исходного вопроса. Тем не менее, я разместил немного более полный пример здесь (все еще есть некоторые вещи, которые можно было бы улучшить жестко ...) - person mata; 09.08.2013
comment
Когда я закрываю QDialog, появляется сообщение об ошибке: QThread: Destroyed, пока поток все еще работает. Есть проблема? - person GSandro_Strongs; 16.10.2015
comment
Вы уверены, что это действительно работает так, как вы думаете? self.simulRunner на самом деле принадлежит создавшему его потоку (поскольку существует экземпляр класса, членом которого он является). Вы должны создать экземпляр simulRunner (без self), подключить все, что хотите подключить (включая добавление self.simulThread.finished.connect(simulRunner.deleteLater) to clean up properly), move it to self.simulThread` и все. По крайней мере, так C++ API (и документация) говорит вам сделать это . - person rbaleksandar; 15.02.2016