Подтвердить что ты не робот

Перенаправление stdout и stderr в PyQt4 QTextEdit из вторичного потока

Переполнение стека. Еще раз, я прихожу к вам в то время, когда вы остро нуждаетесь, неуверенно балансируя на грани безумия. Этот вопрос, как видно из названия, является объединением нескольких других вопросов, которые я видел здесь.

У меня есть приложение PyQt, и я хочу перенаправить потоки stdout и stderr в QTextEdit, который находится в моем графическом интерфейсе без задержки.

Сначала я нашел следующий ответ: qaru.site/info/276685/...

Это работает отлично, но с одной оговоркой: если stdout или stderr обновляются несколько раз, в то время как процессор обрабатывает относительно более длительный метод, все обновления появляются одновременно, когда основной поток возвращается в цикл приложения. К сожалению, у меня есть несколько методов, которые занимают до 20 секунд для завершения (связанные с сетью), и поэтому приложение перестает отвечать на запросы - и QTextEdit не обновляется до тех пор, пока они не будут завершены.

Чтобы устранить эту проблему, я делегировал всю обработку графического интерфейса в основной поток, и я создавал второй поток для обработки более длинных сетевых операций, используя pyqtSignals, чтобы уведомлять основной поток о том, когда работа завершена и возвращает результаты. Сразу же, когда я начал тестировать написанный таким образом код, интерпретатор python начал сбой без предупреждения.

Вот где он сильно размахивает: Python рушится, потому что - используя класс из включенной ссылки выше - я назначил потоки sys.stdout/err в виджет QTextEdit; Виджеты PyQt не могут быть изменены из любого потока, кроме потока приложения, и поскольку обновления для stdout и stderr поступают из созданного мной вторичного рабочего потока, они нарушают это правило. Я прокомментировал раздел кода, в котором я перенаправляю выходные потоки, и, конечно же, программа работает без ошибок.

Это возвращает меня к квадрату и оставляет меня в запутанной ситуации; Предполагая, что я продолжаю обрабатывать связанные с GUI операции в основном потоке и заниматься вычислением и более длительными операциями во вторичном потоке (который я понял, это лучший способ блокировать приложение, когда пользователь вызывает события), как я могу перенаправить Stdout и Stderr из обоих потоков в виджет QTextEdit? Класс в ссылке выше отлично подходит для основного потока, но убивает python - по причине, описанной выше, - когда обновления поступают из второго потока.

4b9b3361

Ответ 1

Во-первых, +1 для понимания того, как thread- небезопасно, многие из примеров!

Решение состоит в том, чтобы использовать потокобезопасный объект (например, Python Queue.Queue) для оповещения передачи информации. Я добавил пример кода ниже, который перенаправляет stdout на Python Queue. Этот Queue считывается с помощью QThread, который передает содержимое в основной поток через механизм сигнала/слота Qt (излучающие сигналы являются потокобезопасными). Затем основной поток записывает текст в текстовое редактирование.

Надеюсь, что это ясно, не стесняйтесь задавать вопросы, если это не так!

EDIT: обратите внимание, что приведенный пример кода не очищает QThreads красиво, поэтому вы будете получать предупреждения, когда вы выходите. Я оставлю это вам, чтобы перейти к вашему прецеденту и очистить поток (-ы)

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_()