Queue.Queue
获取numpy数组(或多个numpy数组),并计算在GUI上显示的内容。然后,计算器通过自定义信号向GUI线程(主线程)发送信号,告诉GUI更新显示的matplotlib图形。我正在使用这里和这里描述的“正确”方法。
因此,这是一般布局。我尝试缩短我的输入时间,在某些部分使用了注释而不是实际代码:
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,它会在主线程中运行函数调用,程序不会挂起。
我还想总结一些问题:
1. QThread能否有事件循环(通过exec_)并具有长时间运行的循环? 2. 如果接收器在信号发出后但未被确认之前断开插槽,BlockingQueuedConnection发射器是否会挂起? 3. app.exec_()后,是否应该等待QThreads(通过thread.wait()),这是必需的吗? 4. 是否有一个由QMainWindow关闭时提供的Qt提供的信号,或者从QApplication那里? 编辑2/进展更新:我已经根据我的需求修改了这篇帖子,创建了一个可运行的问题示例。
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时,它会导致调用者的事件循环停止当前操作并继续处理事件循环中的待处理事件,最终继续长时间的计算事件。其他建议,如this email中建议使用定时器或将工作放在其他线程中,我认为这只会让我的工作变得更加复杂,特别是因为我已经证明(我认为)定时器在我的情况下不起作用。如果processEvents似乎解决了我所有的问题,我稍后会回答自己的问题。