我正在抓住QThread,以便在后台执行可能更长的线程时不锁定我的GUI。我正在尝试练习写一个简单的应用程序:倒数计时器,我可以点击“开始”按钮开始,从而启动倒计时循环,我可以通过点击“暂停”按钮暂停。
我想以“正确的方式”使用QThread(解释over here),即继承QObject,然后通过moveToThread将此子类的实例附加到QThread。我用QTDesigner做了GUI,这是我到目前为止修改过的内容(来自Net上的其他示例):
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()中暂停循环?
提前谢谢!更新
在史蒂夫和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_()
答案 0 :(得分:1)
这种情况正在发生,因为您告诉Qt在单个线程中运行SomeObject
对象的代码。在您的代码中,您有两个线程,主GUI线程和self.objThread
。 pauseLoop()
与longRunning()
在同一个帖子中调用,这意味着longRunning()
必须在pauseLoop()
运行之前完成。相反,您需要从其他线程调用pauseLoop()
,而不是self.objThread
。
请注意,当您开始从两个不同的线程(主线程和新线程)更改数据时,您可能会开始遇到竞争条件。既然你只设置了一个布尔变量,那么你会没事的,但要记住这一点。
编辑:正如three_pineapples
在评论中指出的那样,访问GUI对象(例如:QWidget
)只能在主GUI线程中完成。为了说明这是如何工作的,我更新了这个答案,使用信号来更新GUI线程中的LCD,而不是仅仅将值打印到stdout。我还添加了代码来打印不同部分的当前线程。
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