QTimer не вызывает метод для анимации сюжета - PullRequest
0 голосов
/ 21 марта 2019

Я пытаюсь создать приложение с графическим интерфейсом в PyQt5, которое непрерывно получает данные из удаленного графического интерфейса и готовит исключение с помощью pyqtgraph. Я использую модуль сокета Python для подключения и передачи данных. Я использую многопоточность для GUI и соединения.

Графический интерфейс и прием данных работают нормально, но при обработке полученных данных для построения Qtimer не вызывает метод, который делает анимацию.

import sys
import time
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.uic import loadUi
from PyQt5.QtCore import QTimer
import socket 
from threading import Thread 
from socketserver import ThreadingMixIn 
import numpy as np
import pyqtgraph as pg

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        loadUi('app.ui', self)       
        self.plotWidget.plotItem.showGrid(True, True, 0.2)
        self.serverThread=ServerThread()
        self.serverThread.start()

    def plot(self, amplitude):
        self.y = amplitude * np.random.normal(size=600)
        self.plotWidget.setXRange (0, 2)
        self.plotWidget.setYRange (-0.5, 1)
        Color = pg.mkPen('g', width=1)
        self.curve = self.plotWidget.plot(self.y, pen=Color)
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.update)
        self.timer.start(20)

    #method that performs the animation
    def update(self):
        self.y[:-1] = self.y[1:]  # shift data in the array one sample left
        self.curve.setData(self.y)

class ServerThread(Thread):
    def __init__(self): 
        Thread.__init__(self) 
        self.window=window

     def run(self): 
        TCP_IP = '0.0.0.0' 
        TCP_PORT = 5000 
        BUFFER_SIZE = 20  
        tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
        tcpServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
        tcpServer.bind((TCP_IP, TCP_PORT)) 
        threads = [] 

        tcpServer.listen(1) 
        while True:
            print("Multithreaded Python server : Waiting for connections from TCP clients..") 
            global conn
            (conn, (ip,port)) = tcpServer.accept() 
            newthread = ClientThread(ip,port) 
            newthread.start() 
            threads.append(newthread) 

        for t in threads: 
            t.join()

class ClientThread(Thread): 
    def __init__(self,ip,port): 
        Thread.__init__(self) 
        self.ip = ip 
        self.port = port 
        print("[+] New server socket thread started for " + ip + ":" + str(port))

    def run(self):        
        while True :             
            global conn
            data = conn.recv(2048)
            data_recv = data.decode('utf-8')
            data = int (data_recv)
            window.plot(data)


if __name__ == '__main__':
    app = QApplication(sys.argv)        
    window = Window()
    window.show()  
    sys.exit(app.exec_())

Графический интерфейс показывает сюжет без анимации. В чем может быть проблема? Процессы заблокированы? QTimer в QThread - сборщик мусора? Что я делаю не так?

Если у кого-то есть идея, пожалуйста, помогите мне.

1 Ответ

2 голосов
/ 21 марта 2019

Я думаю, что проблема в том, что вы напрямую вызываете window.plot(data) из потока, который не является основным потоком / потоком GUI, и ожидаете, что QTimer затем сработает для вызова слот-метода в основном потоке / GUI.

Qt не работает таким образом, однако - если вы хотите, чтобы QTimer работал в основном потоке, вам нужно создать его в основном потоке.

Вторая проблема заключается в том, чтовы создаете новый QTimer в каждом вызове window.plot(data) и настраиваете каждый новый QTimer на испускание сигнала каждые 20 мс.Таким образом, даже если бы работала функция QTimer, постоянное накопление десятков / сотен QTimer объектов, каждый из которых излучал сигнал 50 раз в секунду, быстро привело бы к бесполезности вашей программы.

Мое предложение по исправлениюсоздать только один QTimer объект (возможно, внутри Window.__init__(self)), connect() его в вашем update() слоте и вызвать start(20) на нем ровно один раз;этого будет достаточно, чтобы ваш метод self.update() вызывался с частотой 50 Гц (если позволяет мощность процессора).

Остается только вопрос о том, как безопасно передать полученный data из сетевого потока в вашОсновная тема.Для этого, вероятно, самый простой способ - создать подкласс QEvent, который может содержать полученные data в качестве переменной-члена, и заставить ваш сетевой поток создавать новый объект этого подкласса каждый раз, когда ему нужно отправить data в основной поток, и вызовите QApplication.postEvent() с вашим Window и этим QEvent -объектом в качестве аргумента.Затем в своем классе Window переопределите метод event(self, QEvent) (который вызывается всякий раз, когда ваш объект Window получает любое событие) и добавьте туда логику, чтобы, если аргумент-событие является одним из вашего подкласса события, он захватываетdata вне события и обрабатывает его.(Критическим преимуществом здесь является то, что event(self, QEvent) вызывается основным потоком, а не напрямую вашим сетевым потоком, поэтому вы можете безопасно взаимодействовать с объектами основного потока / графического интерфейса из этого контекста)

...