Точный таймер с PyQt - PullRequest
       17

Точный таймер с PyQt

0 голосов
/ 04 ноября 2019

Я использую pyqtgraph для построения огромного количества данных, которые я получаю от датчиков.

Для этого я создал один поток, который получает данные и помещает их в очередь. Чтобы отобразить данные, я периодически проверяю таймером, является ли очередь не пустой. Проблема в том, что точность таймера (QTimer) кажется очень плохой. Я имею в виду, что когда нагрузка в измерительном потоке низкая (бездействие в течение 1000/100 мс), точность довольно хорошая, но когда увеличение нагрузки (бездействие в течение 10 мс), моя функция обновления, используемая для отображения данных, не вызывается с тем жеperiod.

Вот пример кода:

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

Я хочу, чтобы таймер был синхронным с тем же периодом независимо от того,нагрузка на измерительную нить. Я пытался уменьшить приоритет предыдущего потока, но это не решило проблему.

1 Ответ

1 голос
/ 04 ноября 2019

Документация QTimer частично отвечает на ваш вопрос:

Все типы таймеров могут превысить время ожидания, чем ожидалось, если система занята или не может предоставитьЗапрашиваемая точность. В таком случае превышения тайм-аута, Qt выдаст timeout () только один раз, даже если истекло несколько тайм-аутов, и затем возобновит исходный интервал.

Проблема в том, что после того, как вы вызовете _update по истечении времени ожидания Qt потребуется некоторое время для обработки того, что происходит после self.drawplot.setData(), который в основном вычисляет графическую информацию и фактически выводит ее на экран.
Вы не получаете задержку в 1 мс, потому что Qt просто неможет работать так быстро.

Даже если QTimer может работать в другом потоке («асинхронно», но будьте осторожны со значением этого слова), он всегда зависит от того, в каком потоке он создан или находится (QTimer не может быть запущен или остановлен из потока, отличного от его). Итак, поскольку вы создали таймер в потоке окна (основной цикл событий Qt), его точность тайм-аута зависит от способности этого цикла обрабатывать все его событий, а также потому, что многие события связаны между собой. Что касается рисования в графическом интерфейсе (которое кажется быстрым на наш взгляд, но на самом деле медленное , поскольку оно очень требовательно для процессора), вы можете легко понять, почему вы никогда не получите этот интервал в 1 мс. И не забывайте тот факт, что даже если pyqtgraph очень быстрый, мы все еще говорим о Python.

Хотя возможно достижение большей точности для QTimer 1 мс (для него создается отдельный поток), вы быв любом случае от этого не получится никаких преимуществ: даже с очень быстрым компьютером вам необходимо обновить экран на частоте 1000 Гц, в то время как большинство графического оборудования не может работать намного быстрее, чем 100-200 Гц;это означает, что даже если вы владеете высокопроизводительной системой, вы не получите более одного обновления каждые 4 мс.

Если вам нужно обновлять график каждый раз, когда появляются новые данные, вероятно, лучше использоватьсигналы и слоты, что также позволяет избежать ненужной проверки очереди и обеспечивает обновление графика по мере необходимости:

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())

Вы не получите обновление 1 мс, но в этом не будет необходимоститак или иначе;Кроме того, помните, что печать с такой скоростью всегда будет каким-то образом влиять на производительность.

Наконец, нет никакого преимущества в установке PreciseTimer с интервалом 1 мс, поскольку точность таймера на большинстве платформ в любом случае составляет около 1 мс(как объяснено в начале того же параграфа в документации , связанной с ), и установка точности требуется только для более длинных интервалов (я бы сказал, по крайней мере, 25-50 мс).

Здесь также есть интересный ответ о QTimer здесь , объясняющий, что в основном происходит всякий раз, когда вы его создаете, и время его ожидания.

...