2016-01-16 3 views
0

Я получаю доступ к QThread, чтобы не блокировать мой графический интерфейс, выполняя, возможно, более длинные потоки в фоновом режиме. Я пытаюсь на практике написать простое приложение: таймер обратного отсчета, который я могу начать, нажав на кнопку «Пуск», тем самым инициировав цикл обратного отсчета, который я могу приостановить, нажав кнопку «Пауза».Простое применение QThread

Я хочу сделать это «правильным способом» использования QThread (пояснил over here), то есть подклассифицирует QObject, а затем присоединяет экземпляр этого подкласса к QThread через moveToThread. Я сделал GUI с QtDesigner и это то, что я изменил до сих пор (от других примеров в Сети) в Python:

from PyQt4 import QtGui, QtCore 
from PyQt4.QtCore import QThread, SIGNAL 

import time, sys, mydesign 

class SomeObject(QtCore.QObject): 

    def __init__(self, lcd): 
     super(self.__class__, self).__init__() 
     self.lcd = lcd 
     self.looping = True 
    finished = QtCore.pyqtSignal() 
    def pauseLoop(self): 
     print "Hello?" 
     self.looping = False 

    def longRunning(self): 
     count = 10 
     self.lcd.display(count) 
     while count > 0 and self.looping: 
      time.sleep(1) 
      count -= 1 
      self.lcd.display(count) 
     self.finished.emit() 

class ThreadingTutorial(QtGui.QMainWindow, mydesign.Ui_MainWindow): 
    def __init__(self): 
     super(self.__class__, self).__init__() 
     self.setupUi(self) 

     #an instance of SomeObject gets attached to 
     #an instance of wrapper class QThread() 

     #objThread is a wrapper object for an instance of 
     # self-defined class SomeObject 
     self.objThread = QtCore.QThread() 
     self.obj = SomeObject(self.lcdNumber) 
     self.obj.moveToThread(self.objThread) 
     #connect all the signals 
     self.obj.finished.connect(self.objThread.quit) 
     self.objThread.started.connect(self.obj.longRunning) 
     self.startCountdown.clicked.connect(self.objThread.start) 
     self.pauseButton.clicked.connect(self.obj.pauseLoop) 


if __name__ == "__main__": 
    app = QtGui.QApplication(sys.argv) 
    form = ThreadingTutorial() 
    form.show() 
    app.exec_() 

Графический интерфейс пользователя не заблокирован, но функция pauseLoop() только получает казненных после петли законченный. Как мне сделать это, чтобы выполнить мою задачу, чтобы иметь возможность приостановить цикл в longRunning()?

Thx заранее!

обновление:

С помощью Стива и замечаний three_pineapples, я придумал следующее решение, используя внутренний цикл событий в objThread:

from PyQt4 import QtGui, QtCore 
from PyQt4.QtCore import QThread, SIGNAL, QTimer 

import time, sys, mydesign 

class SomeObject(QtCore.QObject): 

    def __init__(self): 
     super(self.__class__, self).__init__() 
     self.looping = True 
     self.count = 10 
     self.timer = QTimer(self) 
     self.timer.start(1000) 
     self.connect(self.timer, SIGNAL("timeout()"), self.longRunning) 

    finished = QtCore.pyqtSignal() 
    iterated = QtCore.pyqtSignal() 
    def pauseLoop(self): 
     self.looping = False 

    def longRunning(self): 
     if self.count > 0 and self.looping: 
      self.count -= 1 
      self.iterated.emit() 
     else: 
      self.finished.emit()# sends signal for stopping event loop 

class ThreadingTutorial(QtGui.QMainWindow, mydesign.Ui_MainWindow): 
    def __init__(self): 
     super(self.__class__, self).__init__() 
     self.setupUi(self) 


     #an instance of SomeObject gets attached to 
     #an instance of wrapper class QThread() 

     #objThread is a wrapper object for an instance of 
     # self-defined class SomeObject 
     self.objThread = QtCore.QThread() 
     self.obj = SomeObject() 
     self.lcdNumber.display(self.obj.count) 
     self.obj.moveToThread(self.objThread) 
     #connect all the signals 
     self.obj.finished.connect(self.objThread.quit) 
     self.obj.iterated.connect(lambda: self.lcdNumber.display(self.obj.count)) 
     self.objThread.started.connect(self.obj.longRunning) 
     self.startCountdown.clicked.connect(self.objThread.start)   
     self.pauseButton.clicked.connect(self.obj.pauseLoop) 


if __name__ == "__main__": 
    app = QtGui.QApplication(sys.argv) 
    form = ThreadingTutorial() 
    form.show() 
    app.exec_() 
+1

Обратите внимание, что вы никогда не должны пытаться использовать объекты GUI из другого потока. Также вы не можете перемещать объект GUI в другой поток. Вы получите случайные сбои приложений (segfaults). Я ваши примеры выше, вы по-прежнему получаете доступ к виджету LCD из вторичной нити, что плохо! Вместо этого вы должны посылать сигнал в основной поток каждый раз, когда хотите обновить виджет, чтобы вы могли безопасно взаимодействовать с графическим интерфейсом. –

+0

Я изменил свой ответ, чтобы проиллюстрировать комментарий three_pineapples. – Steve

+0

Спасибо за хороший момент. Я буду держать это правило в уме. Я также внесу поправки в свое решение по этому правилу. – user2949762

ответ

1

Это происходит потому, что вы говорите Qt для запустите код для вашего объекта SomeObject в одном потоке. В вашем коде у вас есть два потока, ваш основной поток графического интерфейса и ваш self.objThread. pauseLoop() вызывается в той же теме, что и longRunning(), что означает, что longRunning() должен быть завершен до того, как будет запущен pauseLoop(). Вместо этого вам нужно позвонить pauseLoop() из другой темы, а не self.objThread.

Обратите внимание, что при запуске изменения данных из двух разных потоков (основного потока и вашего нового потока) вы можете начать работать в условиях гонки. Поскольку вы устанавливаете только одну логическую переменную, вы будете в порядке, но это то, о чем нужно помнить.

Редактировать: Как было отмечено в комментарии по three_pineapples, доступ к GUI объекты (например: QWidget) должно быть сделано только в главном GUI потоке. Чтобы показать, как это будет работать, я обновил этот ответ, чтобы использовать сигналы для обновления ЖК-дисплея в потоке графического интерфейса вместо того, чтобы просто печатать значение для стандартного вывода. Я также добавил код, чтобы распечатать текущий поток в разных разделах.

from PySide import QtGui, QtCore 
from PySide.QtCore import QThread, SIGNAL 

import time, sys 

class SomeObject(QtCore.QObject): 
    updateCounter = QtCore.Signal(int) 
    finished = QtCore.Signal() 
    def __init__(self): 
     super(self.__class__, self).__init__() 
     self.looping = True 
    def pauseLoop(self): 
     self.looping = False 
     print 'Pause Loop: '+str(QThread.currentThreadId()) 
    def longRunning(self): 
     print 'Long Running: '+str(QThread.currentThreadId()) 
     count = 10 
     while count > 0 and self.looping: 
      count -= 1 
      self.updateCounter.emit(count) 
      time.sleep(1) 
     self.finished.emit() 

class ThreadingTutorial(QtGui.QWidget): 
    def __init__(self): 
     super(self.__class__, self).__init__() 
     # 
     self.thisLayout = QtGui.QVBoxLayout(self) 
     self.startCountdown = QtGui.QPushButton('Start', self) 
     self.pauseButton = QtGui.QPushButton('Stop', self) 
     self.lcd = QtGui.QLabel('', self) 
     self.thisLayout.addWidget(self.startCountdown) 
     self.thisLayout.addWidget(self.pauseButton) 
     self.thisLayout.addWidget(self.lcd) 
     # 
     print 'Main GUI Thread: '+str(QThread.currentThreadId()) 
     self.objThread = QtCore.QThread() 
     self.obj = SomeObject() 
     self.obj.moveToThread(self.objThread) 
     self.obj.updateCounter.connect(self._updateTimer) 
     # 
     self.obj.finished.connect(self.objThread.quit) 
     self.objThread.started.connect(self.obj.longRunning) 
     self.startCountdown.clicked.connect(self.objThread.start) 
     self.pauseButton.clicked.connect(self._stopTimer) 
    def _stopTimer(self): 
     self.obj.pauseLoop() 
    def _updateTimer(self, num): 
     self.lcd.setText(str(num)) 
     print 'Update Timer: '+str(QThread.currentThreadId()) 


if __name__ == "__main__": 
    app = QtGui.QApplication(sys.argv) 
    form = ThreadingTutorial() 
    form.show() 
    app.exec_() 

Пример вывода:

Main GUI Thread: 3074717376 
Long Running: 3034471232 
Update Timer: 3074717376 
Update Timer: 3074717376 
Update Timer: 3074717376 
Update Timer: 3074717376 
Update Timer: 3074717376 
Update Timer: 3074717376 
Pause Loop: 3074717376 
+0

Спасибо Стиву, ваше объяснение имеет смысл. :) Между тем, я также нашел решение, основанное на внутреннем цикле событий objThread. Ради разнообразия я отправлю его ниже. – user2949762

+0

Рад, что вы его работаете, ваш путь, безусловно, лучший способ для вашего проекта, так как вы хотите избежать долго исполняемого кода в потоке графического интерфейса. – Steve

Смежные вопросы