Redireccionando stdout y stderr a un QTextEdit de PyQt4 desde un hilo secundario

Desbordamiento de stack. Una vez más, vengo a ti en un momento de extrema necesidad, tambaleándome precariamente al borde de la locura . Esta pregunta, como puede verse en el título, es una amalgama de varias otras preguntas que he visto 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 stack: 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 aparecen simultáneamente cuando el hilo principal regresa al bucle de la aplicación . Desafortunadamente, tengo algunos métodos que tardan hasta 20 segundos en completarse (relacionados con la red), por lo que la aplicación deja de responder, y QTextEdit no se actualiza, hasta que finalizan.

Para solucionar este problema, delegué todo el procesamiento de la GUI en el subproceso principal, y he estado generando un segundo subproceso para manejar las operaciones de red más largas, usando pyqtSignals para notificar al subproceso principal de cuando el trabajo ha terminado y ha pasado resultados de vuelta. 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 pone muy frustrado: Python se bloquea porque, al usar la clase del enlace incluido arriba, asigné las secuencias sys.stdout / err al widget QTextEdit; Los widgets de PyQt no pueden modificarse desde cualquier subproceso que no sea el subproceso de la aplicación, y dado que las actualizaciones de stdout y stderr provienen del subproceso de trabajo secundario que creé, están violando esta regla. He comentado la sección de código en la que redirecciono los flujos de salida y, efectivamente, el progtwig se ejecuta sin errores.

Esto me devuelve al punto de partida, y me deja en una situación confusa; Suponiendo que continúo manejando las operaciones relacionadas con la GUI en el subproceso principal y lidiando con el cálculo y las operaciones más largas en un subproceso secundario (lo que he llegado a entender es la mejor manera de evitar que la aplicación se bloquee cuando el usuario activa eventos), ¿cómo puedo ¿Redirigir Stdout y Stderr desde ambos hilos al widget QTextEdit? La clase en el enlace anterior 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 darse cuenta de lo inseguros que son muchos de los ejemplos sobre el desbordamiento de stack.

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 ejemplo debajo que redirige 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 la señal / ranura de Qt (la emisión de señales es segura para el hilo). El hilo principal luego escribe el texto en una edición de texto.

Espero que quede claro, siéntase libre de hacer preguntas si no lo es!

EDITAR: tenga en cuenta que el ejemplo de código proporcionado no limpia QThreads muy bien, por lo que obtendrá avisos impresos cuando salga. Dejaré que se extienda a su estuche de uso y limpie 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_()