带有PyQt的精确计时器

问题描述 投票:0回答:1

我正在使用pyqtgraph绘制从传感器接收的大量数据。

为此,我创建了一个线程来获取数据并将其放入队列。为了绘制数据,我定期使用计时器检查队列是否不为空。问题在于计时器(QTimer)的准确性似乎确实很差。我的意思是,当测量线程中的负载较低(睡眠状态为1000/100毫秒)时,精度非常好,但是当负载增加(睡眠状态为10毫秒)时,用于绘制数据的更新函数不会以相同的方式回调期。

这里是示例代码:

import sys
import time
from queue import Queue
from random import random

import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtWidgets

data_queue = Queue()


class WorkerThread(QtCore.QThread):
    def __init__(self, parent):
        super(WorkerThread, self).__init__(parent=parent)

    def run(self):
        t_init = time.time()
        while True:
            # Generating random data
            values = [(time.time()-t_init, random()) for _ in range(200)]
            data_queue.put(values)
            print("adding data")
            self.msleep(10)


class GraphPlot(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(GraphPlot, self).__init__(parent)

        self.mainbox = QtWidgets.QWidget()
        self.setCentralWidget(self.mainbox)
        self.mainbox.setLayout(QtWidgets.QVBoxLayout())

        self.canvas = pg.GraphicsLayoutWidget()
        self.mainbox.layout().addWidget(self.canvas)

        self.analogPlot = self.canvas.addPlot(title='Real-time data')
        self.drawplot = self.analogPlot.plot(pen='r')

        numPoints = 20000
        self.t = np.zeros(numPoints, dtype=int)
        self.x = np.zeros(numPoints, dtype=int)

        self.worker = WorkerThread(self)
        self.worker.start()

        self.timer = pg.QtCore.QTimer()
        self.timer.setTimerType(QtCore.Qt.PreciseTimer)
        self.timer.timeout.connect(self._update)
        self.timer.start(1)

    def _update(self):
        print('start:', time.time())
        size = data_queue.qsize()
        if size > 0:
            for _ in range(size):
                values = data_queue.get()
                for v in values:
                    self.t = np.append(self.t[1:], v[0])
                    self.x = np.append(self.x[1:], v[1])
            self.drawplot.setData(self.t, self.x)        
        print('end:', time.time())


app = QtWidgets.QApplication(sys.argv)
plot = GraphPlot()
plot.show()
sys.exit(app.exec_())

输出摘录:

start: 1572893919.9067862
adding data
end: 1572893919.9217482    <-- 
adding data
start: 1572893919.9586473  <-- there should be 1ms of difference with last 'end'
                               actually, there is around 37ms

无论测量线程上的负载如何,我都希望计时器与同一时间段同步。我试图降低前一个线程的优先级,但未能解决问题。

python pyqt pyqtgraph
1个回答
0
投票

QTimer documentation部分回答了您的问题:

所有计时器类型可能如果系统繁忙,则超时时间比预期的要晚或无法提供所需的准确性。在这种情况下溢出,Qt将仅发出timeout()一次,即使有多个超时已过期,然后将恢复原始间隔。

问题是,从超时中调用_update之后,Qt将需要一些时间来处理self.drawplot.setData()之后发生的事情,这基本上是在计算图形信息并将其实际绘制在屏幕上。您没有得到1ms的延迟,因为Qt不能这么快地工作。

即使QTimer可以在另一个线程中工作(“异步”,但要注意该词的含义),它始终取决于它创建或驻留的线程(无法从其他线程启动或停止QTimer)比它的一个)。因此,由于您已经在窗口线程(Qt主事件循环)中创建了计时器,因此其超时精度取决于该循环处理其[[all事件的能力,并且由于许多事件与GUI相关绘画(在我们看来似乎很快,但实际上对CPU的要求很高,[),您可以轻松地理解为什么永远都不会获得1ms的间隔。并且不要忘记一个事实,即使pyqtgraph非常快,我们仍然在谈论Python。

虽然有可能达到1ms QTimer更好的精度(为此创建一个单独的线程),但无论如何您都不会从中获得任何好处:即使使用非常快的计算机,您实质上要求的是更新屏幕以1000Hz的频率运行,而大多数图形硬件的运行速度却不能超过100-200Hz;这意味着,即使您拥有高端系统,每4ms也不会获得多个更新。如果需要在每次有新数据可用时都需要更新图,最好使用信号和插槽,这也可以避免对队列进行任何不必要的检查,并确保根据需要更新图:

class WorkerThread(QtCore.QThread): newData = QtCore.pyqtSignal(object) def __init__(self, parent): super(WorkerThread, self).__init__(parent=parent) def run(self): t_init = time.time() while True: # Generating random data values = [(time.time()-t_init, random()) for _ in range(200)] print("adding data") self.newData.emit(values) self.msleep(10) class GraphPlot(QtWidgets.QMainWindow): def __init__(self, parent=None): super(GraphPlot, self).__init__(parent) self.mainbox = QtWidgets.QWidget() self.setCentralWidget(self.mainbox) self.mainbox.setLayout(QtWidgets.QVBoxLayout()) self.canvas = pg.GraphicsLayoutWidget() self.mainbox.layout().addWidget(self.canvas) self.analogPlot = self.canvas.addPlot(title='Real-time data') self.drawplot = self.analogPlot.plot(pen='r') numPoints = 20000 self.t = np.zeros(numPoints, dtype=int) self.x = np.zeros(numPoints, dtype=int) self.worker = WorkerThread(self) self.worker.newData.connect(self.newData) self.worker.start() def newData(self, data): print('start:', time.time()) for v in data: self.t = np.append(self.t[1:], v[0]) self.x = np.append(self.x[1:], v[1]) self.drawplot.setData(self.t, self.x) print('end:', time.time())

您将不会获得1ms的更新,但是无论如何都不需要它;另外,请记住,以该速度打印将始终以某种方式影响性能。

最后,以1ms的间隔设置PreciseTimer是没有优势的,因为无论如何,大多数平台上的计时器精度约为1ms(如文档linked before的同一段开头所述),并且设置仅在更长的时间间隔内才需要精度(我说至少25-50ms)。

关于QTimer here,还有一个有趣的答案,它解释了每次创建一个超时都会发生的基本情况。

© www.soinside.com 2019 - 2024. All rights reserved.