Таймер в дочернем потоке в PySide

Во-первых, я очень новичок в Python и Pyside. Чтобы немного самосовершенствоваться, я пытаюсь заставить QTimer выполняться каждую секунду в дочернем потоке моей программы PySide (на данный момент я просто хочу, чтобы он печатал «привет!» на терминал каждую секунду без зависания главного окна).

Я попытался преобразовать пример, найденный в Qt Wiki, с C++ на Python/PySide, но поскольку я действительно не знаю С++, я предполагаю, что преобразовал его неправильно, и поэтому он не работает должным образом.

На данный момент кажется, что функция doWork() выполняется только один раз и больше никогда. Что я делаю не так? Есть ли лучший способ выполнять функцию каждую секунду в PySide без зависания главного окна?

Вот код (для большей ясности я удалил некоторый код главного окна):

from PySide import QtGui
from PySide import QtCore
from client_gui import Ui_MainWindow

statsThread = QtCore.QThread()

class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        #setup GUI
        self.setupUi(self)
        #start thread to update GUI
        self.statsThread = updateStatsThread()
        self.statsThread.start(QtCore.QThread.TimeCriticalPriority)

class updateGuiWithStats(QtCore.QObject):
    def Worker(self):
        timer = QtCore.QTimer()
        timer.timeout.connect(self.doWork())
        timer.start(1000)

    def doWork(self):
        print "hi!"

class updateStatsThread (QtCore.QThread):
    def run(self):
        updater = updateGuiWithStats()
        updater.Worker()
        self.exec_()

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    frame = MainWindow()
    frame.show()
    sys.exit(app.exec_())

person James Mackenzie    schedule 25.03.2012    source источник


Ответы (2)


@Masci уже указал на исправление, которое вам нужно для вашего timer.timeout.connect, но я вижу больше проблем, чем просто это.

Нет необходимости создавать глобальный QThread, который никогда не используется:

statsThread = QtCore.QThread()

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

class UpdateGuiWithStats(QtCore.QObject):

    def startWorker(self):
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.doWork)
        self.timer.start(1000)

Кроме того, используйте UpperCase для первой буквы классов и camelCase для методов. Вы делаете смесь обоих способов.

Пара заметок, основанных на этой ссылке, которую вы предоставили, вашем примере и других комментариях здесь... Вы можете использовать только QTimer в качестве решения, если ваш doWork() очень легкий и не будет блокировать ваш основной цикл событий кучей данных хруст, сон и т. д. Если это так, то doWork() нужно будет переместить в QThread, как это делает ваш пример. Но в этот момент нет необходимости использовать цикл событий и QTimer в отдельном классе, который вызывает свою собственную работу. Все это можно объединить в один класс, например:

class UpdateStatsThread(QtCore.QThread):

    def __init__(self, parent=None):
        super(UpdateStatsThread, self).__init__(parent)
        self._running = False

    def run(self):
        self._running = True
        while self._running:
            self.doWork()
            self.msleep(1000)

    def stop(self, wait=False):
        self._running = False
        if wait:
            self.wait()

    def doWork(self):
        print "hi!"
person jdi    schedule 25.03.2012
comment
Вы правы, предполагая, что я хотел бы сделать больше в doWork(), чем просто вызвать print "foo" или что-то в этом роде. Имея это в виду, я реализовал пересмотренный класс UpdateStatsThread(), который вы написали. Однако, когда я собираюсь выйти из потока, я получаю сообщение о том, что поток был уничтожен во время работы. Я предполагаю, что это из-за того, что я вышел из потока, пока time.sleep(1) все еще работает. Есть ли хороший способ выйти из темы и избежать этого сообщения? - person James Mackenzie; 26.03.2012
comment
Я полностью. Это определенно потому, что python уничтожает объект до того, как он действительно завершится. Я обновил свой ответ, добавив необязательное ожидание в метод stop(). Я также обновил сон, чтобы использовать QThread.msleep() - person jdi; 26.03.2012

в классе updateGuiWithStats метод Worker:

timer.timeout.connect(self.doWork())

должно быть

timer.timeout.connect(self.doWork)

Вы подключаете сигнал тайм-аута к None (возвращаемое значение метода doWork()), и я думаю, именно поэтому он выполняется только один раз: doWork вызывается во время соединения и больше. Когда вы устанавливаете соединения, не забудьте соединить имя функции (в словах Pythonics, вызываемый объект), а не вызов функции.

Кстати, даже если вышеуказанное решило вашу проблему, вам следует избегать использования потоков, поскольку QTimer уже сам делает то, что вам нужно. В документах, на которые вы ссылаетесь, первый ответ на вопрос Когда я не должен использовать потоки? вопрос: Таймеры.

person Masci    schedule 25.03.2012
comment
Кроме того, решение использовать QTimer вместо QThread не является универсальным решением. Это работает только в том случае, если ваш doWork() быстрый, поэтому он может выполняться, а не блокировать основной цикл событий. Если это тяжелый вызов, он все равно будет блокировать ваш цикл событий каждый раз, когда он вызывается. Таким образом, вам затем нужно будет переместить эту функциональность в поток. - person jdi; 25.03.2012
comment
Согласен, но для нужд ОП я думаю, что многопоточность излишняя. - person Masci; 25.03.2012
comment
Какое-то безосновательное предположение. Откуда ты знаешь, чем на самом деле занимается его doWork()? Очевидно, это не просто оператор печати. Это был просто его пример кода. Потребности ОП вполне могут потребовать потока - person jdi; 25.03.2012