保持QThread响应需要time.sleep()吗?
首先,我是Python的新手。我之前一直在用MatLab(我是工程师,不是计算机科学家),现在开始尝试把Python、NumPy、SciPy等融入我的工作流程。所以,请原谅我对这个很棒的编程语言的无知!
作为我的第一次尝试,我决定开发一个应用程序来与我正在开发的传感器进行交互。这个传感器的分辨率是微秒级的(每500微秒会有512个高能和512个低能“像素”的数据),但输入输出会是阻塞的。因为我会不断地查询这个设备,所以我知道使用线程很重要,这样可以保持图形界面(GUI)响应迅速(最终这个GUI还会与另一个设备进行串行通信,并有一个处理传感器数据的图像处理子程序)。我创建了一个使用线程的MatPlotLib实例来绘制这些来自传感器的“实时”数据。虽然我已经独立构建了与传感器通信的模块,并确认我知道如何在Python中做到这一点,但我现在只是通过生成512个在8到12之间的随机数来模拟低能“像素”,以及512个在90到110之间的随机数来模拟高能“像素”。这就是我使用线程的部分。从这里的许多例子中,我还学会了使用blitting来让MatPlotLib的屏幕更新足够快——但是,问题是,除非我让线程休眠20毫秒(使用time.sleep(0.02)
),否则GUI就会无响应。可以验证的是,MatPlotLib的交互式X,Y数据点反馈无法工作,而‘停止’按钮也无法用来中断这个过程。任何超过time.sleep(0.02)
的时间都会让GUI运行得更流畅,但代价是“数据速率”。任何低于time.sleep(0.02)
的时间都会让GUI无响应。我不太明白为什么。我本来想去试试GUIqwt,但在放弃MatPlotLib之前想在这里问问,因为我不确定这是否真的是问题所在。我担心让线程休眠20毫秒会导致我错过至少40行来自传感器阵列的数据(40行 * 500微秒/行 = 20毫秒)。
这是我当前的代码:
import time, random, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class ApplicationWindow(QMainWindow):
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.thread = Worker()
self.create_main_frame()
self.create_status_bar()
self.connect(self.thread, SIGNAL("finished()"), self.update_UI)
self.connect(self.thread, SIGNAL("terminated()"), self.update_UI)
self.connect(self.startButton, SIGNAL("clicked()"), self.start_acquisition)
self.connect(self.stopButton, SIGNAL("clicked()"), self.stop_acquisition)
self.thread.pixel_list.connect(self.update_figure)
def create_main_frame(self):
self.main_frame = QWidget()
self.dpi = 100
self.width = 10
self.height = 8
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,120))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.canvas.draw()
self.background = None
self.lE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.hE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
self.startButton = QPushButton(self.tr("&Start"))
self.stopButton = QPushButton(self.tr("&Stop"))
layout = QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.mpl_toolbar, 1, 0)
layout.addWidget(self.startButton, 2, 0)
layout.addWidget(self.stopButton, 2, 1)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("XRTdev Interface"))
def create_status_bar(self):
self.status_text = QLabel("I am a status bar. I need a status to show!")
self.statusBar().addWidget(self.status_text, 1)
def start_acquisition(self):
self.thread.exiting = False
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.thread.render()
def stop_acquisition(self):
self.thread.exiting = True
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def update_figure(self, lE, hE):
if self.background == None:
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.canvas.restore_region(self.background)
self.lE_line.set_ydata(lE)
self.hE_line.set_ydata(hE)
self.axes.draw_artist(self.lE_line)
self.axes.draw_artist(self.hE_line)
self.canvas.blit(self.axes.bbox)
def update_UI(self):
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def cleanup_UI(self):
self.background = None
self.axes.clear()
self.canvas.draw()
class Worker(QThread):
pixel_list = pyqtSignal(list, list)
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def __del__(self):
self.exiting = True
self.wait()
def render(self):
self.start()
def run(self):
# simulate I/O
n = random.randrange(100,200)
while not self.exiting and n > 0:
lE = [random.randrange(5,16) for i in xrange(512)]
hE = [random.randrange(80,121) for i in xrange(512)]
self.pixel_list.emit(lE, hE)
time.sleep(0.02)
n -= 1
def main():
app = QApplication(sys.argv)
form = ApplicationWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()
也许我的问题根本不在于MatPlotLib或PyQT4,而是我实现线程的方式。正如我所说,我是新手,正在学习。而且,我甚至不确定GUIqwt是否能解决这些问题——但我知道我在这里看到过很多推荐,建议使用比MatPlotLib更快的东西来在GUI中进行“实时”绘图。谢谢大家的帮助!
1 个回答
[因为QThread
让人困惑,所以进行了编辑]
使用QThread
的方法似乎有两种:一种是继承它(就像你的例子和文档中所说的那样),另一种是创建一个工作对象,然后把它移动到一个线程中(可以参考这篇博客)。当你把信号和槽混合在一起时,我就更困惑了。正如Avaris所说,这个变化可能不是你的问题所在。
我把你的Worker
类重新改写成了QObject
的子类(因为这是我理解的风格)。
一个问题是,如果你在你的假数据系统中不加一个sleep
,那么你会在不到1秒的时间内生成所有回调到主窗口的请求。这样主线程就会被阻塞,因为它需要清理信号队列。如果你把延迟设置为你指定的延迟(0.0005秒),那么数据生成的速度会远远快于显示的速度,这似乎表明这可能不适合你的问题(从实际出发,你也看不见那么快的画面,30到40帧每秒的速度似乎就不错)。
import time, random, sys
#from PySide.QtCore import *
#from PySide.QtGui import *
from PyQt4 import QtCore
from PyQt4 import QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class ApplicationWindow(QtGui.QMainWindow):
get_data = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.thread = QtCore.QThread(parent=self)
self.worker = Worker(parent=None)
self.worker.moveToThread(self.thread)
self.create_main_frame()
self.create_status_bar()
self.startButton.clicked.connect(self.start_acquisition)
self.stopButton.clicked.connect(self.stop_acquisition)
self.worker.pixel_list.connect(self.update_figure)
self.worker.done.connect(self.update_UI)
self.get_data.connect(self.worker.get_data)
self.thread.start()
def create_main_frame(self):
self.main_frame = QtGui.QWidget()
self.dpi = 100
self.width = 10
self.height = 8
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,120))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.canvas.draw()
self.background = None
self.lE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.hE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
self.startButton = QtGui.QPushButton(self.tr("&Start"))
self.stopButton = QtGui.QPushButton(self.tr("&Stop"))
layout = QtGui.QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.mpl_toolbar, 1, 0)
layout.addWidget(self.startButton, 2, 0)
layout.addWidget(self.stopButton, 2, 1)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("XRTdev Interface"))
def create_status_bar(self):
self.status_text = QtGui.QLabel("I am a status bar. I need a status to show!")
self.statusBar().addWidget(self.status_text, 1)
def start_acquisition(self):
self.worker.exiting = False
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.get_data.emit()
def stop_acquisition(self):
self.worker.exiting = True
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def update_figure(self, lE, hE):
if self.background == None:
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.canvas.restore_region(self.background)
self.lE_line.set_ydata(lE)
self.hE_line.set_ydata(hE)
self.axes.draw_artist(self.lE_line)
self.axes.draw_artist(self.hE_line)
self.canvas.blit(self.axes.bbox)
def update_UI(self):
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def cleanup_UI(self):
self.background = None
self.axes.clear()
self.canvas.draw()
class Worker(QtCore.QObject):
pixel_list = QtCore.pyqtSignal(list, list)
done = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtCore.QObject.__init__(self, parent)
self.exiting = True
@QtCore.pyqtSlot()
def get_data(self):
# simulate I/O
print 'data_start'
n = random.randrange(100,200)
while not self.exiting and n > 0:
lE = [random.randrange(5,16) for i in xrange(512)]
hE = [random.randrange(80,121) for i in xrange(512)]
self.pixel_list.emit(lE, hE)
time.sleep(0.05)
n -= 1
print 'n: ', n
self.done.emit()