values - python threadwithreturnvalue
Redireccionando stdout y stderr a un PyQt4 QTextEdit desde un hilo secundario (1)
Desbordamiento de pila. Una vez más, vengo a ti en un momento de gran necesidad, tambaleándote precariamente al borde de la locura . Esta pregunta, como puede ser evidente por el título, es una amalgama de varias otras preguntas que he visto respondidas aquí.
Tengo una aplicación PyQt, y quiero redireccionar las secuencias stdout y stderr a un QTextEdit que está en mi GUI sin demora .
Inicialmente, encontré la siguiente respuesta de desbordamiento de pila: https://stackoverflow.com/a/17145093/629404
Esto funciona perfectamente, pero con una advertencia: si stdout o stderr se actualizan varias veces mientras la CPU está procesando un método relativamente más largo, todas las actualizaciones se muestran simultáneamente cuando el hilo principal regresa al bucle de la aplicación . Desafortunadamente, tengo algunos métodos que toman hasta 20 segundos para completar (relacionados con la red), por lo que la aplicación deja de responder y el QTextEdit no se actualiza hasta que finalizan.
Para solucionar este problema, delegué todo el procesamiento de GUI en el hilo principal, y he estado generando un segundo hilo para manejar las operaciones de red más largas, usando pyqtSignals para notificar el hilo principal de cuando el trabajo está terminado y pasar resultados posteriores Inmediatamente, cuando comencé a probar el código escrito de esta manera, el intérprete de Python comenzó a fallar sin previo aviso.
Aquí es donde se vuelve muy frustrante: Python se cuelga porque, usando la clase del enlace incluido anterior, he asignado las secuencias sys.stdout / err al widget QTextEdit; Los widgets de PyQt no se pueden modificar a partir de ningún hilo que no sea el de la aplicación, y dado que las actualizaciones de stdout y stderr provienen del hilo de trabajo secundario que creé, están violando esta regla. He comentado la sección de código en la que redirijo los flujos de salida y, por supuesto, el programa se ejecuta sin errores.
Esto me lleva de vuelta al principio, y me deja en una situación confusa; Suponiendo que continúo manejando las operaciones relacionadas con la GUI en el hilo principal y tratando con el cómputo y las operaciones más largas en un hilo secundario (que he llegado a entender que es la mejor manera de evitar que la aplicación bloquee cuando el usuario desencadena eventos), ¿cómo puedo redirigir Stdout y Stderr de ambos hilos al widget QTextEdit? La clase en el enlace de arriba funciona bien para el hilo principal, pero mata a Python, por el motivo descrito anteriormente, cuando las actualizaciones provienen del segundo hilo.
En primer lugar, +1 para darnos cuenta de cuán inseguros son los muchos ejemplos de desbordamiento de pila.
La solución es usar un objeto seguro para subprocesos (como Python Queue.Queue
) para mediar la transferencia de información. He adjuntado un código de muestra a continuación que redirecciona stdout
a una Queue
Python. Esta Queue
es leída por un QThread
, que emite el contenido al hilo principal a través del mecanismo de señal / ranura de Qt (la emisión de señales es segura para hilos). El hilo principal luego escribe el texto en una edición de texto.
Espero que sea claro, ¡siéntete libre de hacer preguntas si no es así!
EDITAR: tenga en cuenta que el ejemplo de código proporcionado no limpia muy bien QThreads, por lo que recibirá avisos cuando salga. Te dejo extender tu caso de uso y limpiar el (los) hilo (s)
import sys
from Queue import Queue
from PyQt4.QtCore import *
from PyQt4.QtGui import *
# The new Stream Object which replaces the default stream associated with sys.stdout
# This object just puts data in a queue!
class WriteStream(object):
def __init__(self,queue):
self.queue = queue
def write(self, text):
self.queue.put(text)
# A QObject (to be run in a QThread) which sits waiting for data to come through a Queue.Queue().
# It blocks until data is available, and one it has got something from the queue, it sends
# it to the "MainThread" by emitting a Qt Signal
class MyReceiver(QObject):
mysignal = pyqtSignal(str)
def __init__(self,queue,*args,**kwargs):
QObject.__init__(self,*args,**kwargs)
self.queue = queue
@pyqtSlot()
def run(self):
while True:
text = self.queue.get()
self.mysignal.emit(text)
# An example QObject (to be run in a QThread) which outputs information with print
class LongRunningThing(QObject):
@pyqtSlot()
def run(self):
for i in range(1000):
print i
# An Example application QWidget containing the textedit to redirect stdout to
class MyApp(QWidget):
def __init__(self,*args,**kwargs):
QWidget.__init__(self,*args,**kwargs)
self.layout = QVBoxLayout(self)
self.textedit = QTextEdit()
self.button = QPushButton(''start long running thread'')
self.button.clicked.connect(self.start_thread)
self.layout.addWidget(self.textedit)
self.layout.addWidget(self.button)
@pyqtSlot(str)
def append_text(self,text):
self.textedit.moveCursor(QTextCursor.End)
self.textedit.insertPlainText( text )
@pyqtSlot()
def start_thread(self):
self.thread = QThread()
self.long_running_thing = LongRunningThing()
self.long_running_thing.moveToThread(self.thread)
self.thread.started.connect(self.long_running_thing.run)
self.thread.start()
# Create Queue and redirect sys.stdout to this queue
queue = Queue()
sys.stdout = WriteStream(queue)
# Create QApplication and QWidget
qapp = QApplication(sys.argv)
app = MyApp()
app.show()
# Create thread that will listen on the other end of the queue, and send the text to the textedit in our application
thread = QThread()
my_receiver = MyReceiver(queue)
my_receiver.mysignal.connect(app.append_text)
my_receiver.moveToThread(thread)
thread.started.connect(my_receiver.run)
thread.start()
qapp.exec_()