2016-01-16 29 views
0

爲了不鎖定我的GUI,同時在後臺執行可能較長的線程,我正在抓緊QThread。我正在嘗試編寫一個簡單的應用程序:一個倒計時器,我可以通過點擊「開始」按鈕來啓動,從而啓動一個倒計時循環,通過點擊「暫停」按鈕可以暫停。QThread的簡單應用

我想用「合適的方式」使用QThread(解釋爲over here),即子類化QObject,然後通過moveToThread將此子類的實例附加到QThread。我做了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_() 

的GUI沒有被鎖定,但該功能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小部件,這是不好的!您應該在每次要更新小部件時向主線程發出信號,以便您可以安全地與GUI進行實際交互。 –

+0

我修改了我的答案來說明three_pineapples的評論。 – Steve

+0

謝謝你的好處。我會牢記這一規則。我也會修改我對這條規則的解決方案。 – user2949762

回答

1

發生這種情況,因爲你告訴Qt來在單個線程中運行您的SomeObject對象的代碼。在你的代碼中,你有兩個線程,你的主GUI線程和你的self.objThreadpauseLoop()在與longRunning()相同的線程中調用,這意味着longRunning()必須在pauseLoop()可以運行之前完成。相反,您需要從另一個線索調用pauseLoop(),而不是self.objThread

請注意,當您開始更改來自兩個不同線程(主線程和新線程)的數據時,您可能會開始遇到競態條件。既然你只設置了一個布爾變量,你會沒事的,但要記住。

編輯:由於three_pineapples在評論中指出,訪問GUI對象(例如:QWidget)只應在主界面線程來完成。爲了說明這是如何工作的,我已經更新了這個答案,使用信號來更新GUI線程中的LCD,而不是僅僅將值打印到標準輸出。我還添加了代碼以在不同部分打印當前線程。

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

很高興你的工作,你的方式肯定是你的項目更好的方式,因爲你想避免在GUI線程中長執行代碼。 – Steve