我有一个有三个线程的PyQt4 GUI。一个线程是一个数据源,它提供了数据的numpy数组。下一个线程是一个计算线程,它通过Python Queue.Queue
获取numpy数组(或多个numpy数组),并计算将在GUI上显示的内容。然后计算器通过一个自定义信号向GUI线程(主线程)发出信号,这告诉GUI更新显示的matplotlib图形。PyQt4:当GUI关闭时中断QThread exec
所以这里的总体布局。我试图缩短我的打字时间和使用的意见,而不是在一些地方的实际代码:
class Source(QtCore.QObject):
signal_finished = pyQtSignal(...)
def __init__(self, window):
self._exiting = False
self._window = window
def do_stuff(self):
# Start complicated data generator
for data in generator:
if not self._exiting:
# Get data from generator
# Do stuff - add data to Queue
# Loop ends when generator ends
else:
break
# Close complicated data generator
def prepare_exit(self):
self._exiting = True
class Calculator(QtCore.QObject):
signal_finished = pyQtSignal(...)
def __init__(self, window):
self._exiting = False
self._window = window
def do_stuff(self):
while not self._exiting:
# Get stuff from Queue (with timeout)
# Calculate stuff
# Emit signal to GUI
self._window.signal_for_updating.emit(...)
def prepare_exit(self):
self._exiting = True
class GUI(QtCore.QMainWindow):
signal_for_updating = pyQtSignal(...)
signal_closing = pyQtSignal(...)
def __init__(self):
self.signal_for_updating.connect(self.update_handler, type=QtCore.Qt.BlockingQueuedConnection)
# Other normal GUI stuff
def update_handler(self, ...):
# Update GUI
def closeEvent(self, ce):
self.fileQuit()
def fileQuit(self): # Used by a menu I have File->Quit
self.signal_closing.emit() # Is there a builtin signal for this
if __name__ == '__main__':
app = QtCore.QApplication([])
gui = GUI()
gui.show()
source_thread = QtCore.QThread() # This assumes that run() defaults to calling exec_()
source = Source(window)
source.moveToThread(source_thread)
calc_thread = QtCore.QThread()
calc = Calculator(window)
calc.moveToThread(calc_thread)
gui.signal_closing.connect(source.prepare_exit)
gui.signal_closing.connect(calc.prepare_exit)
source_thread.started.connect(source.do_stuff)
calc_thread.started.connect(calc.do_stuff)
source.signal_finished.connect(source_thread.quit)
calc.signal_finished.connect(calc_thread.quit)
source_thread.start()
calc_thread.start()
app.exec_()
source_thread.wait() # Should I do this?
calc_thread.wait() # Should I do this?
...所以,我的所有问题,当我试图关闭GUI源完成之前发生的,当我让数据生成器完成它关闭罚款:
在等待线程时,程序挂起。据我所知,这是因为闭合信号的连接插槽永远不会被其他线程的事件循环运行(它们被困在“无限”运行的do_stuff方法中)。
当计算线程发出GUI结束后右的更新GUI信号(BlockedQueuedConnection信号),它似乎挂起。我猜这是因为GUI已经关闭,不能接受发射的信号(根据我在实际代码中输入的打印信息判断)。
我一直在浏览大量的教程和文档,我只是觉得我在做一些愚蠢的事情。这是可能的,有一个事件循环和一个“无限”的运行循环结束早期......并安全地(资源正确关闭)?
我也好奇我BlockedQueuedConnection的问题(如果我的描述是有道理的),但这个问题可能是与我没有看到一个简单的重新设计可以解决的。
感谢您的帮助,让我知道什么是没有意义的。如果需要的话,我也可以添加更多的代码,而不是仅仅做评论(我有点希望我做了一些愚蠢的事情,而不需要)。
编辑:我发现了一些周围的工作是什么,但是,我想我只是幸运,它屡试不爽至今。如果我使用prepare_exit和thread.quit连接DirectConnections,它将在主线程中运行函数调用,并且程序不会挂起。
我也想我应该总结的一些问题:
- 一个的QThread能有一个事件循环(通过exec_),并有一个长期运行的循环?
- 是否一个BlockingQueuedConnection发射挂起如果接收机断开槽(信号被发射之后,但在此之前人们承认)?
- 我应该等待QThreads(通过thread.wait())app.exec_后(),这是需要的?
- 是否有一个Qt为QMainWindow的关闭时提供的信号,或者是有一个从所述的QApplication?
编辑2 /进度情况:我已经适应this post我的需要造成的问题的一个可运行的例子。
from PyQt4 import QtCore
import time
import sys
class intObject(QtCore.QObject):
finished = QtCore.pyqtSignal()
interrupt_signal = QtCore.pyqtSignal()
def __init__(self):
QtCore.QObject.__init__(self)
print "__init__ of interrupt Thread: %d" % QtCore.QThread.currentThreadId()
QtCore.QTimer.singleShot(4000, self.send_interrupt)
def send_interrupt(self):
print "send_interrupt Thread: %d" % QtCore.QThread.currentThreadId()
self.interrupt_signal.emit()
self.finished.emit()
class SomeObject(QtCore.QObject):
finished = QtCore.pyqtSignal()
def __init__(self):
QtCore.QObject.__init__(self)
print "__init__ of obj Thread: %d" % QtCore.QThread.currentThreadId()
self._exiting = False
def interrupt(self):
print "Running interrupt"
print "interrupt Thread: %d" % QtCore.QThread.currentThreadId()
self._exiting = True
def longRunning(self):
print "longRunning Thread: %d" % QtCore.QThread.currentThreadId()
print "Running longRunning"
count = 0
while count < 5 and not self._exiting:
time.sleep(2)
print "Increasing"
count += 1
if self._exiting:
print "The interrupt ran before longRunning was done"
self.finished.emit()
class MyThread(QtCore.QThread):
def run(self):
self.exec_()
def usingMoveToThread():
app = QtCore.QCoreApplication([])
print "Main Thread: %d" % QtCore.QThread.currentThreadId()
# Simulates user closing the QMainWindow
intobjThread = MyThread()
intobj = intObject()
intobj.moveToThread(intobjThread)
# Simulates a data source thread
objThread = MyThread()
obj = SomeObject()
obj.moveToThread(objThread)
obj.finished.connect(objThread.quit)
intobj.finished.connect(intobjThread.quit)
objThread.started.connect(obj.longRunning)
objThread.finished.connect(app.exit)
#intobj.interrupt_signal.connect(obj.interrupt, type=QtCore.Qt.DirectConnection)
intobj.interrupt_signal.connect(obj.interrupt, type=QtCore.Qt.QueuedConnection)
objThread.start()
intobjThread.start()
sys.exit(app.exec_())
if __name__ == "__main__":
usingMoveToThread()
您可以通过运行该代码,并在两种连接类型之间的交换上interrupt_signal,直接连接工作看,因为它在一个单独的线程运行,正确或错误的做法?我觉得这是不好的做法,因为我正在快速改变另一个线程正在阅读的内容。该QueuedConnection不起作用,因为事件循环必须等待,直到事件循环获取前回绕到中断信号,这是不是我想要的longRunning完成。
编辑3:我记得读取QtCore.QCoreApplication.processEvents
可以与长时间运行的计算的情况下使用,但我读到的一切说除非你知道自己在做什么,不使用它。那么这里是什么,我认为它在做什么(在一定意义上),并使用它似乎工作:当你打电话processEvents它会导致调用者的事件循环hault其当前操作,并继续处理事件循环的未决事件,最终继续长计算事件。像this email其他建议建议定时器或投入其他线程的工作,我认为这只是让我的工作更加复杂,尤其是因为我已经证明了(我认为)计时器没有在我的情况下工作。如果processEvents似乎解决了我所有的问题,我会在稍后回答我自己的问题。
我不明白所有的代码(它失控了)。 Questons:1.为什么不把exec_与长时间运行的循环混合? 2.为什么不使用processEvents?它应该用于什么(我永远无法在任何地方找到这个答案)? 3.我试图用一面旗帜标记事情应该放弃,但不想锁定它。我不想锁定它,因为这是QThread由GUI通过信号“配置”的一个基本示例(尽管这可以通过许多不同的方式完成)。我也厌倦了你构建自己的线程框架来使用PyQt4的事实。 – daveydave400
我也应该注意到我设计的GUI是用于实时更新数据图。我的意思是通过套接字发送数据,并将其分析并放入一个python生成器,该生成器传递给我的GUI代码,该代码遍历生成器并更新matplotlib图。 – daveydave400
我学到的一课:不要在线程间使用生成器!这导致执行流程交叉线程,导致更大的混乱,然后你应该处理。 –