【问题标题】:Python - PyQt: Memory issue with QThreadPython - PyQt:QThread 的内存问题
【发布时间】:2018-11-14 15:08:58
【问题描述】:

我想构建一个 Qt 接口来控制相机的采集。

我想要什么: 在进入硬件通信之前,我正在测试一个控制“假相机”的 GUI,这是一个连续循环,如果启动,每 100 毫秒会给出一个随机图像。图像采集在单独的线程中运行,以便用户可以与 GUI 交互。用户可以通过按钮开始和停止采集。

我想怎么做: 我的第一次尝试是简单地创建一个QThread 并调用run() 方法,然后该方法将包含一个无限循环,其中单个图像采集由QThread.sleep(0.1) 交错。我注意到在停止并重新启动线程后,程序开始滞后并在一段时间后崩溃。 通过阅读周围的一些帖子和主要的Qt webpage,我了解到做我想做的事情的最好方法是:

子类QObject 来创建一个worker。实例化这个工人 对象和QThread。将工作线程移动到新线程。

此外,按照this 帖子中的想法,我添加了一个QTimer 对象以无限期地迭代线程内的工作线程,并且我实现了一个active 标志,它只会让线程运行而无需执行任何操作(如果已设置)到False。 这个解决方案似乎在一开始就奏效了。我可以根据需要多次开始、停止和重新开始采集。

问题:

1) 当相机未获取时,CPU 总是占用相当多的资源(在我的情况下大约为 30%,根据 windows 任务管理器)。

2) 有时,在开始采集后,内存开始被填满,就像每个新图像都分配到新内存中(虽然我猜它应该被覆盖),直到程序变得无响应然后崩溃。 下图是我在任务管理器中看到的这种情况: 红色箭头对应于采集开始的时间。

我哪里做错了?这是正确的前进方式吗?

代码

from PyQt5 import QtCore, QtWidgets
import sys
import numpy as np
import matplotlib
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg


class MyWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('MyWindow')
        self._main = QtWidgets.QWidget()
        self.setCentralWidget(self._main) 

        # generate matplotlib canvas
        self.fig = matplotlib.figure.Figure(figsize=(4,4))
        self.canvas = FigureCanvasQTAgg(self.fig)
        self.ax = self.fig.add_subplot(1,1,1)
        self.im = self.ax.imshow(np.zeros((1000, 1000)), cmap='viridis')
        self.im.set_clim(vmin=0,vmax=1) 
        self.canvas.draw()

        # Add widgets and build layout
        self.startcambutton = QtWidgets.QPushButton('Start', checkable=True)
        self.startcambutton.released.connect(self.acquire)
        self.contincheck = QtWidgets.QCheckBox("Continuous")
        self.contincheck.clicked.connect(self.continuous_acquisition)
        self.contincheck.setChecked(True)
        layout = QtWidgets.QGridLayout(self._main)
        layout.addWidget(self.canvas, 0, 0)
        layout.addWidget(self.startcambutton, 1, 0)
        layout.addWidget(self.contincheck, 2, 0)

        # Initialize worker and timer and moveToThread
        self.fake_camera_thread = QtCore.QThread()
        self.fake_camera_timer = QtCore.QTimer()
        self.fake_camera_timer.setInterval(0)
        self.fake_camera_worker = FakeCamera(self)
        self.fake_camera_worker.moveToThread(self.fake_camera_thread)
        self.fake_camera_timer.timeout.connect(self.fake_camera_worker.acquire)
        self.fake_camera_thread.started.connect(self.fake_camera_timer.start)
        self.fake_camera_thread.finished.connect(self.fake_camera_worker.deleteLater)
        self.fake_camera_thread.finished.connect(self.fake_camera_timer.deleteLater)
        self.fake_camera_thread.finished.connect(self.fake_camera_thread.deleteLater)
        self.fake_camera_thread.start()

        self.camera_thread = self.fake_camera_thread
        self.camera = self.fake_camera_worker
        self.camera.image.connect(self.image_acquired)

    def continuous_acquisition(self):
        if self.contincheck.isChecked(): self.startcambutton.setCheckable(True)
        else: self.startcambutton.setCheckable(False)

    def acquire(self):
        if self.startcambutton.isCheckable() and not self.startcambutton.isChecked():
            self.startcambutton.setText('Start')
            self.contincheck.setEnabled(True)
        elif self.startcambutton.isCheckable() and self.startcambutton.isChecked():
            self.startcambutton.setText('Stop')
            self.contincheck.setDisabled(True)
        self.camera.toggle()

    @QtCore.pyqtSlot(object)
    def image_acquired(self, image):
        self.im.set_data(image)
        self.canvas.draw()


    def closeEvent(self, event):
        """ If window is closed """
        self.closeApp()
        event.accept() # let the window close

    def closeApp(self):
        """ close program """
        self.camera_thread.quit()
        self.camera_thread.wait()
        self.close()
        return



class FakeCamera(QtCore.QObject):
    image = QtCore.pyqtSignal(object)

    def __init__(self, parent):
        QtCore.QObject.__init__(self)
        self.parent = parent
        self.active = False

    def toggle(self):
        self.active = not self.active

    def acquire(self):
        """ this is the method running indefinitly in the associated thread """
        if self.active:
            self.new_acquisition()

    def new_acquisition(self):
        noise = np.random.normal(0, 1, (1000, 1000))
        self.image.emit(noise)
        if not self.parent.startcambutton.isChecked():
            self.active = False
        QtCore.QThread.sleep(0.1)



if __name__ == '__main__':
    app = QtCore.QCoreApplication.instance()
    if app is None:
        app = QtWidgets.QApplication(sys.argv)
    mainGui = MyWindow()
    mainGui.show()
    app.aboutToQuit.connect(app.deleteLater)
    app.exec_()

【问题讨论】:

    标签: python multithreading pyqt pyqt5


    【解决方案1】:

    QThread.sleep() 只接受整个参数,当传递一个浮点数时,它将舍入它,在你的情况下,0.1 将舍入为 0,因此没有暂停,因此信号将连续发出,但绘画需要一段时间,所以它将存储在队列中的数据会增加内存。另一方面,如果QTimer 将连续调用一个任务,最好住在处理任务的对象的线程中,这样QTimer 是FakeCamera 的儿子就足够了。另一个改进是使用了装饰器@QtCore.pyqtSlot(),因为连接是在 C++ 中给出的,因此更高效。最后我改进了设计,因为FakeCamera 不应该直接与 GUI 交互,因为如果你想将它与另一个 GUI 一起使用,你将不得不修改大量代码,而不是创建插槽。

    from PyQt5 import QtCore, QtWidgets
    import numpy as np
    import matplotlib
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
    
    
    class MyWindow(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            self.setWindowTitle('MyWindow')
            self._main = QtWidgets.QWidget()
            self.setCentralWidget(self._main) 
    
            # generate matplotlib canvas
            self.fig = matplotlib.figure.Figure(figsize=(4,4))
            self.canvas = FigureCanvasQTAgg(self.fig)
            self.ax = self.fig.add_subplot(1,1,1)
            self.im = self.ax.imshow(np.zeros((1000, 1000)), cmap='viridis')
            self.im.set_clim(vmin=0,vmax=1) 
            self.canvas.draw()
    
            # Add widgets and build layout
            self.startcambutton = QtWidgets.QPushButton('Start', checkable=True)
            self.startcambutton.released.connect(self.acquire)
            self.contincheck = QtWidgets.QCheckBox("Continuous")
            self.contincheck.toggled.connect(self.startcambutton.setCheckable)
            self.contincheck.setChecked(True)
            layout = QtWidgets.QGridLayout(self._main)
            layout.addWidget(self.canvas, 0, 0)
            layout.addWidget(self.startcambutton, 1, 0)
            layout.addWidget(self.contincheck, 2, 0)
    
            # Initialize worker and timer and moveToThread
            fake_camera_thread = QtCore.QThread(self)
            self.fake_camera_worker = FakeCamera()
            self.fake_camera_worker.moveToThread(fake_camera_thread)
            self.startcambutton.toggled.connect(self.fake_camera_worker.setState)
            self.fake_camera_worker.image.connect(self.image_acquired)
            fake_camera_thread.started.connect(self.fake_camera_worker.start)
            fake_camera_thread.finished.connect(self.fake_camera_worker.deleteLater)
            fake_camera_thread.finished.connect(fake_camera_thread.deleteLater)
            fake_camera_thread.start()
    
        @QtCore.pyqtSlot()
        def acquire(self):
            if self.startcambutton.isCheckable():
                text = "Stop" if self.startcambutton.isChecked() else "Start"
                self.startcambutton.setText(text)
                self.contincheck.setEnabled(not self.startcambutton.isChecked())
    
        @QtCore.pyqtSlot(object)
        def image_acquired(self, image):
            self.im.set_data(image)
            self.canvas.draw()
    
    
    class FakeCamera(QtCore.QObject):
        image = QtCore.pyqtSignal(object)
    
        def __init__(self, parent=None):
            super(FakeCamera, self).__init__(parent)
            self.active = False
            self.fake_camera_timer = QtCore.QTimer(self, interval=0)
            self.fake_camera_timer.timeout.connect(self.acquire)
    
        @QtCore.pyqtSlot()
        def start(self):
            self.fake_camera_timer.start()
    
        @QtCore.pyqtSlot(bool)
        def setState(self, state):
            self.active = state
    
        @QtCore.pyqtSlot()
        def toggle(self):
            self.active = not self.active
    
        @QtCore.pyqtSlot()
        def acquire(self):
            """ this is the method running indefinitly in the associated thread """
            if self.active:
                self.new_acquisition()
            QtCore.QThread.msleep(100)
    
        def new_acquisition(self):
            noise = np.random.normal(0, 1, (1000, 1000))
            self.image.emit(noise)
    
    
    if __name__ == '__main__':
        import sys
        app = QtCore.QCoreApplication.instance()
        if app is None:
            app = QtWidgets.QApplication(sys.argv)
        mainGui = MyWindow()
        mainGui.show()
        app.aboutToQuit.connect(app.deleteLater)
        sys.exit(app.exec_())
    

    【讨论】:

    • 感谢所有建议。一如既往地具有启发性!
    猜你喜欢
    • 1970-01-01
    • 2011-05-05
    • 2014-11-19
    • 2014-08-19
    • 2021-01-01
    • 1970-01-01
    • 2016-07-17
    • 2017-09-10
    • 1970-01-01
    相关资源
    最近更新 更多