Przekierowanie stdout i stderr do PyQt4 QTextEdit z drugiego wątku

Przepełnienie stosu. Po raz kolejny, przychodzę do Ciebie w czasie ogromnej potrzeby, tętniąc niepewnie na krawędzi szaleństwa . To pytanie - jak widać z tytułu-jest połączeniem kilku innych pytań, na które odpowiedziałem tutaj.

Mam aplikację PyQt i chcę przekierować strumienie stdout i stderr do QTextEdit, który jest w moim GUI bez opóźnienia .

Początkowo znalazłem następującą odpowiedź na przepełnienie stosu: https://stackoverflow.com/a/17145093/629404

Działa to doskonale, ale z jednym zastrzeżeniem: jeśli stdout lub stderr są aktualizowane wielokrotnie, podczas gdy procesor przetwarza stosunkowo dłuższą metodę, Wszystkie aktualizacje pojawiają się jednocześnie, gdy główny wątek powróci do pętli aplikacji. Niestety, mam kilka metod, które trwają do 20 sekund (związane z siecią), a więc aplikacja przestaje odpowiadać - a QTextEdit nie aktualizuje się - dopóki nie skończą.

Aby rozwiązać ten problem, przekazałem całe przetwarzanie GUI do głównego wątku, a ja odradzam drugi wątek, aby obsłużyć dłuższe operacje sieciowe, używając pyqtSignals, aby powiadomić główny wątek o zakończeniu pracy i przekazać wyniki z powrotem. natychmiast, kiedy zacząłem testować kod napisany w ten sposób, interpreter Pythona zaczął się zawieszać bez żadnego ostrzeżenia.

Tutaj robi się bardzo frustrująco: Python jest upaść, ponieważ-używając klasy z załączonego linku powyżej-przypisałem sys.strumienie stdout / err do widżetu QTextEdit; widżetów PyQt nie można modyfikować z żadnego innego wątku niż wątek aplikacji, a ponieważ aktualizacje stdout i stderr pochodzą z drugiego wątku roboczego, który utworzyłem, naruszają one tę regułę. skomentowałem sekcję kodu, w której Przekierowuję strumienie wyjściowe i oczywiście program działa bez błąd.

To sprowadza mnie z powrotem do punktu wyjścia i pozostawia mnie w mylącej sytuacji; zakładając, że nadal będę obsługiwać operacje związane z GUI w głównym wątku i zajmować się obliczeniami i dłuższymi operacjami w wątku wtórnym (co zrozumiałem, jest najlepszym sposobem, aby utrzymać aplikację przed blokowaniem, gdy użytkownik uruchamia zdarzenia), Jak mogę przekierować Stdout i Stderr z obu wątków do widżetu QTextEdit? Klasa w linku powyżej działa dobrze dla głównego wątku, ale zabija Pythona - z powodów opisanych powyżej-gdy aktualizacje pochodzą z drugiego wątku.

Author: Community, 2014-01-12

1 answers

Po Pierwsze, +1 za uświadomienie sobie, jak thread - unsafe wiele przykładów na stack overflow jest!

Rozwiązaniem jest użycie obiektu bezpiecznego dla wątku (takiego jak Python Queue.Queue) do pośredniczenia w przesyłaniu informacji. Poniżej załączam przykładowy kod, który przekierowuje stdout do Pythona Queue. To {[3] } jest odczytywane przez QThread, które emituje zawartość do głównego wątku poprzez mechanizm sygnału/gniazda Qt (emitujące sygnały są bezpieczne dla wątku). Główny wątek następnie zapisuje tekst do tekstu edycja.

Mam nadzieję, że to jasne, nie krępuj się zadawać pytań, jeśli nie jest!

EDIT: zauważ, że podany przykład kodu nie oczyszcza QThreads ładnie, więc Ostrzeżenia zostaną wydrukowane po zakończeniu pracy. Zostawię to tobie, aby rozszerzyć Twój przypadek użycia i wyczyścić wątek(y)

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_()
 20
Author: three_pineapples,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-12 06:21:52