2011-08-18 118 views
3

我有一个有三个线程的PyQt4 GUI。一个线程是一个数据源,它提供了数据的numpy数组。下一个线程是一个计算线程,它通过Python Queue.Queue获取numpy数组(或多个numpy数组),并计算将在GUI上显示的内容。然后计算器通过一个自定义信号向GUI线程(主线程)发出信号,这告诉GUI更新显示的matplotlib图形。PyQt4:当GUI关闭时中断QThread exec

我使用了“正确”的方法中所描述herehere

所以这里的总体布局。我试图缩短我的打字时间和使用的意见,而不是在一些地方的实际代码:

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. 我应该等待QThreads(通过thread.wait())app.exec_后(),这是需要的?
  4. 是否有一个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似乎解决了我所有的问题,我会在稍后回答我自己的问题。

回答

2

我诚实地没有读取所有的代码。我建议不要在代码中出现循环,而是一次运行每个逻辑块。信号/插槽也可以作为这些东西的透明队列。

我已经写了一些生产者/消费者的示例代码 https://github.com/epage/PythonUtils/blob/master/qt_producer_consumer.py 更先进的utils的一些不同的线程代码,我已经写了 https://github.com/epage/PythonUtils/blob/master/qt_error_display.py

是的,我使用的环路,主要用于举例的目的,但有时你不能避免它们(如从管道读取)。您可以使用QTimer的超时时间为0或有一个标志来标记事情应该退出并用互斥锁保护它。

RE编辑1: 1.不要将exec_与长时间运行的循环混合使用 3. PySide要求您在退出线程后等待。 4.我不记得有一个,你可以将它设置为Destroy On Close,然后监视关闭,或者你可以从QMainWindow继承,覆盖closeEvent并发出一个信号(就像我在qt_error_display.py示例中那样)

RE编辑2: 我建议使用默认的连接类型。

RE编辑3:不要使用processEvents。

+0

我不明白所有的代码(它失控了)。 Questons:1.为什么不把exec_与长时间运行的循环混合? 2.为什么不使用processEvents?它应该用于什么(我永远无法在任何地方找到这个答案)? 3.我试图用一面旗帜标记事情应该放弃,但不想锁定它。我不想锁定它,因为这是QThread由GUI通过信号“配置”的一个基本示例(尽管这可以通过许多不同的方式完成)。我也厌倦了你构建自己的线程框架来使用PyQt4的事实。 – daveydave400

+0

我也应该注意到我设计的GUI是用于实时更新数据图。我的意思是通过套接字发送数据,并将其分析并放入一个python生成器,该生成器传递给我的GUI代码,该代码遍历生成器并更新matplotlib图。 – daveydave400

+0

我学到的一课:不要在线程间使用生成器!这导致执行流程交叉线程,导致更大的混乱,然后你应该处理。 –

2

通过邮件列表归档看后,谷歌搜索,堆栈溢出搜索和思考什么,我的问题真的是什么这个问题的目的是我想出了这样的回答:

简短的回答是使用processEvents()。漫长的回答是,我所有的搜索结果都会让人们“使用processEvents()非常小心”,并且“不惜一切代价避免它”。我认为应该避免使用它,因为在GUI主线程中看不到结果足够快。在这种情况下,不是使用processEvents,而是在主线程中完成的非UI用途的工作应该移动到另一个线程(如我的设计所做的那样)。

我的具体问题需要processEvents()的原因是我希望我的QThreads与GUI线程有双向通信,这意味着我的QThreads必须有一个事件循环(exec_())来接受来自GUI。这种双向交流就是我之前所说的“问题的目的”。由于我的QThreads是为了运行“同时”与主界面线程因为他们需要更新的GUI和图形用户界面(在我的第一个例子退出/关闭信号)进行“更新”,他们需要processEvents()。我认为这是processEvents()的用途。

我对processEvents()的理解,如上所述,当在QThread中调用时,它将阻止/暂停当前事件(我的longRunning方法),同时继续处理事件循环中的事件(仅限于QThread processEvents()被调用)。在经历未决事件之后,事件循环回绕并继续运行暂停的事件(我的longRunning方法)。

我知道我没有回答我所有的问题,但主要的一个回答。

请纠正我,如果我错了以任何方式

编辑:请阅读Ed的回答和评论。