¿Como evitar loops infinitos en GUIs?



  • Que tal,

    Estoy utilizando PySide2 para un proyecto donde recibo datos por comunicación serial desde un microcontrolador.

    Hasta el momento he utilizado este pseudo-código para recibir datos:

    abrir_comunicacion_serial()
    while comunicacion_serial:
        datos = recibir_datos()
    
        si datos == 'end':
            break
        sleep(1)
    
    cerramos_comunicacion_serial()
    procesamos_datos()
    

    El loop while se ejecuta mientras tenga comunicación serial, recibo los datos y evalúo si los datos que recibí son igual a un marcador ('end'), de ser asi salgo del while y continuo con el procesamiento de los datos.

    Funciona, sin embargo la GUI no responde mientras se ejecuta el while infinito, he intentado mandar la recepción de datos a un thread corriendo como daemon (tengo entendido que se ejecuta en el background) pero no he tenido éxito ya que no tengo decidido si los datos los debo procesar en el mismo thread o usar un flag y procesarlos en el thread principal.

    ¿Algún consejo para evitar utilizar loops infinitos que causen que la GUI se torne torpe/no responsiva?

    Saludos!


  • Moderators

    Hola

    Yo diría que tienes que procesar los datos en el hilo secundario, si lo haces en el principal tendrás el mismo atasco en la GUI.

    Un saludo



  • Mejor utiliza los SIGNALS y SLOTS.



  • @Carlos-Diaz said in ¿Como evitar loops infinitos en GUIs?:

    ¿Algún consejo para evitar utilizar loops infinitos que causen que la GUI se torne torpe/no responsiva?

    Nunca usar loops infinitos en Qt/PyQt. Como sugirieron antes, para eso están Signals & Slots



  • Gracias a todos por su respuesta, pude hacer una pequeña prueba de concepto donde utilizo threads, signals y slots, la documentación de Qt es muy clara al respecto (Signals and Slots).

    Este es el código que tengo, no estoy muy familiarizado con Python, debe de haber formas más optimas de realizarlo.

    from PySide2.QtCore import *
    from PySide2.QtWidgets import *
    
    import serial
    import serial.tools.list_ports;
    
    from threading import Thread
    
    import sys
    import time
    
    class serialComm(QObject):
        opened = Signal()
        closed = Signal()
        data_received = Signal(str)
    
        def open(self):
            # Obtiene la lista de puertos seriales de la PC
            available_ports = serial.tools.list_ports.comports()
    
            if available_ports:
                for port in available_ports:
                    if 'COM14' in port.device:
                        self.serial_port = serial.Serial(port.device, 115200)
                        if self.serial_port.is_open:
                            self.opened.emit()
                        else:
                            print('Falla al establecer comunicación con el microcontrolador')
    
            serialReaderThread = Thread(target = self.read, daemon = True)
            serialReaderThread.start()
            
        def close(self):
            if self.serial_port.is_open:
                self.serial_port.close()
                if self.serial_port.is_open == False:
                    self.closed.emit()
    
        def write(self, data):
            self.serial_port.write(data.encode())
    
        def read(self):
            while True:
                while self.serial_port.is_open:
                    data = self.serial_port.readline()
                    print(data)
                    self.data_received.emit(data.decode())
    
                    if 'end' in data.decode():
                        break
    
                    time.sleep(.5)
    
                break
    
            print('All serial data received')
    
    @Slot()
    def print_hello():
        print('Puerto serial abierto')
    
    @Slot()
    def print_goodbye():
        print('Puerto serial cerrado')
    
    app = QApplication([])
    text_area = QTextEdit()
    
    @Slot(str)
    def display_data(msg):
        text_area.append(msg)
    
    if __name__ == "__main__":
    
        my_serial = serialComm()
        my_serial.opened.connect(print_hello)
        my_serial.closed.connect(print_goodbye)
        my_serial.data_received.connect(display_data)
    
        my_serial.open()
    
        # GUI
        layout = QVBoxLayout()
        layout.addWidget(text_area)
        
        window = QWidget()
        window.setLayout(layout)
        window.show()
    
        sys.exit(app.exec_())
    

    Y una captura del script corriendo:
    alt text

    ¿Se podría 'mejorar' el código?

    Saludos



  • Hola, no soy programador de Python, soy de C++.
    Como te dije antes, utiliza los SIGNALS y SLOTS. Estas utilizando un Thread con un sleep de 5ms y esto consume mucha CPU. Conecta el SIGNAL "readyRead" del puerto serie "my_serial" y en el SLOT procesa lo que tengas que hacer. Si los datos que recibes tienen que ser procesados y el proceso es muy complejo luego si que deberías pensar en utilizar Threads pero si no lo es te aconsejo que dejes el procesado de datos en el Main Thread.

    Ejecuta tu programa y mira cuanta CPU está utilizando (si eliminas el sleep lo verás mejor). Es un simple programa que lee del puerto serie y seguro que la CPU está alta(dependerá del numero de CPUs que tengas. Si por ejemplo tienes 4 CPUs verás que tu programa casi llega al 25% de CPU que es el uso de todo un núcleo). Te recomiendo evitar siempre que puedas los sleeps dentro de un thread.



  • Hola,

    Miré con el administrador de tareas la CPU que consume el script al ejecutarse y consume ~2.5%, aunque no estoy seguro de que esta sea la forma más segura de consultar esos datos. Igual revisaré el consumo en la PC donde se va a ejecutar el script, es más vieja entonces debería de consumir más recursos.

    @ollarch said in ¿Como evitar loops infinitos en GUIs?:

    Hola, no soy programador de Python, soy de C++.
    Como te dije antes, utiliza los SIGNALS y SLOTS. Estas utilizando un Thread con un sleep de 5ms y esto consume mucha CPU. Conecta el SIGNAL "readyRead" del puerto serie "my_serial" y en el SLOT procesa lo que tengas que hacer. Si los datos que recibes tienen que ser procesados y el proceso es muy complejo luego si que deberías pensar en utilizar Threads pero si no lo es te aconsejo que dejes el procesado de datos en el Main Thread.

    El "problema" es que el binding de QSerialPort no esta disponible en PySide2 y estoy utilizando el módulo pyserial, igual creé tres señales, opened, closed y data_received con la que comunico mi clase serialComm con la aplicación, voy a investigar si hay forma de que pyserial me diga cuando hay datos disponibles y asi evitar tener el while True.

    Saludos


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.