PyQt5 - QThread 和 QTimer - 为什么它们会干扰?

时间:2021-08-01 17:50:36

标签: python qt pyqt5

我有两个 Python 模块,每个模块都包含一个计时器。

第一个模块从操纵杆读取:

# QThrottle.py
from dataclasses import dataclass

import pygame
from PyQt5.QtCore import QObject, QTimer, pyqtSlot, pyqtSignal


@dataclass
class JData:
    # 7 axes
    axis = [0.0] * 7
    # 16 buttons
    btn = [0] * 16


class QThrottle(QObject):
    # signals
    axis_changed = pyqtSignal([int, float])
    button_changed = pyqtSignal([int, int])

    def __init__(self, parent=None):
        super(QThrottle, self).__init__(parent)

        # container to store last data to compare them with the new ones
        self.j_data = JData()

        pygame.joystick.init()

        # sim_timer to run the simulator
        self.timer = QTimer()
        self.timer_start()

    @pyqtSlot()
    def on_timer_timeout(self):
        # only continue if one device is plugged in
        if pygame.joystick.get_count() < 1:
            return

        # only one joystick is foreseen here
        joystick = pygame.joystick.Joystick(0)
        joystick.init()

        # check all axes for changes
        def check_axes(id):
            val = joystick.get_axis(id)
            if self.j_data.axis[id] != val:
                self.axis_changed.emit(id, val)
                self.j_data.axis[id] = val
        num_axes = range(joystick.get_numaxes())
        # list(map(check_axes, num_axes))

        # check all buttons for changes
        def check_buttons(id):
            val = joystick.get_button(i)

            if self.j_data.btn[i] != val:
                # print('button {id}: {val}'.format(id=i, val=val))
                self.button_changed.emit(i, val)
                self.j_data.btn[i] = val
        num_buttons = range(joystick.get_numbuttons())
        # list(map(check_buttons, num_buttons))

    def timer_start(self):
        self.timer.timeout.connect(self.on_timer_timeout)
        # set the refresh time
        self.timer.start(1)

    def timer_stop(self):
        self.timer.timeout.disconnect(self.on_timer_timeout)
        self.timer.stop()

第二个模块目前模拟一些串行输入:

# QCom.py
from math import cos, sin, pi

from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot


class QCom(QObject):
    # signals
    received_val1 = pyqtSignal(float)
    received_val2 = pyqtSignal(float)

    def __init__(self, parent=None):
        super(QCom, self).__init__(parent)
        # first variables
        self.counter = 0

        # sim_timer to run the simulator
        self.sim_timer = QTimer()

    # ##### Simulator #####
    @pyqtSlot()
    def on_timer_timeout(self):
        self.counter += 1
        self.counter %= 360
        self.received_val1.emit(50 + 50 * sin(self.counter * pi / 180))
        self.received_val2.emit(50 + 50 * cos(self.counter * pi / 180))

    def sim_start(self):
        self.sim_timer.timeout.connect(self.on_timer_timeout)
        self.sim_timer.start(10)

    def sim_stop(self):
        self.sim_timer.timeout.disconnect(self.on_timer_timeout)
        self.sim_timer.stop()

主要加载两个模块,qml-fronend 和 qml-backend:

# main.py
from PyQt5.QtCore import QThread
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
from QCom import QCom
from QThrottle import QThrottle

from QmlWindow.MainWindowBackend import MainWindowBackend

if __name__ == "__main__":
    import sys

    # Create an instance of the application
    app = QGuiApplication(sys.argv)

    thread_com = QThread(app)
    thread_com.start()
    com = QCom()
    com.moveToThread(thread_com)
    com.sim_start()

    thread_throttle = QThread(app)
    thread_throttle.start()
    throttle = QThrottle()
    throttle.moveToThread(thread_throttle)

    # Create QML engine
    engine = QQmlApplicationEngine()
    # Create a window object
    backend = MainWindowBackend()
    # And register it in the context of QML
    engine.rootContext().setContextProperty("backend", backend)
    # Load the qml file into the engine
    engine.load('QmlWindow/MainWindow.qml')

    com.received_val1.connect(backend.received_val1.emit)
    com.received_val2.connect(backend.received_val2.emit)

    engine.quit.connect(app.quit)
    sys.exit(app.exec_())

QML 的东西在这里似乎并不重要。

一开始我是这样加载两个模块的:

    com = QCom()
    throttle = QThrottle()

而且我观察到即使在低计时器频率下我的 qml 前端也有一些抽搐的动作。尽管事实上 QCom 和 QML 之间没有通过信号连接。

我认为最好在单独的线程中运行两个模块并进行以下更改:

    thread_com = QThread(app)
    thread_com.start()
    com = QCom()
    com.moveToThread(thread_com)
    com.sim_start()

    thread_throttle = QThread(app)
    thread_throttle.start()
    throttle = QThrottle()
    throttle.moveToThread(thread_throttle)

我检查了 QThread.currentThreadId() 内的 on_timer_timeout() 以查看处理程序是否在单独的线程上运行,并且确实如此。但抽搐仍然存在。在我看来,两个计时器都会干扰。但我预计如果它们都在不同的线程中运行,情况就不应该如此。

如何解决这个问题?


QML 部分:

# MainWindowBackend.py
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot


class MainWindowBackend(QObject):
    def __init__(self):
        QObject.__init__(self)

    # Signals which shall be handled by qml
    # corresponding slots are implemented in MainWindow.qml -> appWindow -> Connections
    # the preferred argument name shall be 'val' to ease handling
    received_val1 = pyqtSignal(float, arguments=['val'])
    received_val2 = pyqtSignal(float, arguments=['val'])
// MainWindow.qml
import QtQuick 2.12
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3


ApplicationWindow {
    id: appWindow

    visible: true
    title: "MyPanel"
    property int margin: 11
    width: mainLayout.implicitWidth + 2 * margin
    height: mainLayout.implicitHeight + 2 * margin
    minimumWidth: mainLayout.Layout.minimumWidth + 2 * margin
    minimumHeight: mainLayout.Layout.minimumHeight + 2 * margin

    ColumnLayout {
        id: mainLayout
        anchors.fill: parent
        anchors.margins: appWindow.margin

        RowLayout {
            id: eLayout
            EBox {
                eId: "1"
                id: e1Box
            }
            EBox {
                eId: "2"
                id: e2Box
            }
        }
    }

    // connect backend to boxes (eng 1 & 2)
    // the slot on Python side:  mySlot
    // corresponds to qml side:  onMySlot
    Connections {
        target: backend

        onReceived_val1: { e1Box.eng = val }
        onReceived_val2: { e2Box.eng = val }
    }
}
// EBox.qml
import QtQuick 2.12
import QtQuick.Controls 1.4
import QtQuick.Extras 1.4
import QtQuick.Layouts 1.3


GroupBox {
    property string eId: "n"
    title: "Dev " + eId

    // properties which are intended to be connected to the backend
    property double eng: 08.15

    ColumnLayout {
        anchors.fill: parent
        Rectangle {
            Layout.fillWidth: true
            Layout.minimumWidth: egauge.width
            Layout.minimumHeight: egauge.height
            Layout.preferredWidth: 200
            Layout.preferredHeight: 100

            StdGauge {
                id: egauge
                value: eng
                label: "Unit"
                title: "Title"
            }
        }
    }
}
// StdGauge.qml
import QtQuick 2.12
import QtQuick.Controls 1.4
import QtQuick.Extras 1.4
import QtQuick.Layouts 1.3


CircularGauge {
    id: cgauge
    value: 0
    property string label: ""
    property string title: ""
    anchors.centerIn: parent

    Rectangle {
        id: display
        anchors.centerIn: parent
        width: 0.4*(parent.width<parent.height?parent.width:parent.height)
        height: width
        color: "black"
        border.color: "gray"
        border.width: 3
        radius: width*0.5
        Text {
            id: dispValue
            anchors.centerIn: parent
            font.pointSize: 16
            font.bold: true
            color: "gray"
            text: cgauge.value.toFixed(2)
        }
        Text {
            id: dispLabel
            anchors.top: dispValue.bottom
            anchors.horizontalCenter: dispValue.horizontalCenter
            font.pointSize: 10
            font.bold: true
            color: "gray"
            text: cgauge.label
        }
    }
    Text {
        id: dispTitle
        anchors.top: display.bottom
        anchors.bottom: cgauge.bottom
        anchors.horizontalCenter: cgauge.horizontalCenter
        font.pointSize: 16
        font.bold: true
        verticalAlignment: Text.AlignVCenter
        color: "gray"
        text: cgauge.title
    }
}

附注:

我知道,那个电话

        joystick = pygame.joystick.Joystick(0)
        joystick.init()

in on_timer_timeout 很耗时,应该以更优雅的方式解决。但这不是这个问题的重点。

0 个答案:

没有答案