Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre qt (publicaciones antiguas, página 14)

PyQt Quickie: Que no te lleve el basurero

Qt tie­ne sus me­ca­nis­mos pa­ra crear y eli­mi­nar ob­je­tos (el ár­bol de QOb­jec­ts, smart poin­ter­s, etc.) y Py­Qt usa Py­tho­n, así que tie­ne gar­ba­ge co­llec­tio­n.

Con­si­de­re­mos un ejem­plo sim­ple:

from PyQt4 import QtCore

def finished():
    print "El proceso termino!"
    # Salir de la aplicación
    QtCore.QCoreApplication.instance().quit()

def launch_process():
    # Hacer algo asincrono
    proc = QtCore.QProcess()
    proc.start("/bin/sleep 3")
    # Cuando termine, llamar a finished
    proc.finished.connect(finished)

def main():
    app = QtCore.QCoreApplication([])
    # Lanzar el proceso
    launch_process()
    app.exec_()

main()

Si eje­cu­tás eso, te va a pa­sar es­to:

QProcess: Destroyed while process is still running.
El proceso termino!

Encima el script no termina nunca. ¡Diversión! El problema es que proc está siendo borrado al final de launch_process porque no hay más referencias a él.

És­ta es una me­jor ma­ne­ra de ha­cer­lo:

from PyQt4 import QtCore

processes = set([])

def finished():
    print "El proceso termino!"
    # Salir de la aplicación
    QtCore.QCoreApplication.instance().quit()

def launch_process():
    # Hacer algo asincrono
    proc = QtCore.QProcess()
    processes.add(proc)
    proc.start("/bin/sleep 3")
    # Cuando termine, llamar a finished
    proc.finished.connect(finished)

def main():
    app = QtCore.QCoreApplication([])
    # Lanzar el proceso
    launch_process()
    app.exec_()

main()

Al agregar un processes global y meter ahí proc, mantenemos siempre una referencia, y el programa funciona. Sin embargo, sigue teniendo un problema: nunca eliminamos los objetos QProcess.

Si bien en es­te ca­so la pér­di­da de me­mo­ria es muy bre­ve por­que el pro­gra­ma ter­mi­na en­se­gui­da, en un pro­gra­ma de ver­dad es­to no es bue­na idea.

Así que necesitamos agregar una manera de sacar proc de processes cuando no lo necesitemo. Esto no es tan fácil como parece. Por ejemplo, esto no funciona bien:

def launch_process():
    # Hacer algo asincrono
    proc = QtCore.QProcess()
    processes.add(proc)
    proc.start("/bin/sleep 3")
    # Sacamos el proceso del global cuando no lo necesitamos
    proc.finished.connect(lambda: processes.remove(proc))
    # Cuando termine, llamar a finished
    proc.finished.connect(finished)

¡En esta versión, todavía tenemos un memory leak de proc, aunque processes esté vacío! Lo que pasa es que el lambda contiene una referencia a proc.

No tengo una my buena respuesta para este problema que no involucre convertir todo en miembros de un Qbject y usar sender para saber cuál proceso es el que termina, o usar QSignalMapper. Esa versión la dejo como ejercicio para el lector ;-)


Contents © 2000-2023 Roberto Alsina