我有一個有三個線程的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
我學到的一課:不要在線程間使用生成器!這導致執行流程交叉線程,導致更大的混亂,然後你應該處理。 –