从辅助线程将stdout和stderr重定向到PyQt4 QTextEdit [英] Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread

查看:112
本文介绍了从辅助线程将stdout和stderr重定向到PyQt4 QTextEdit的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

堆栈溢出.我再次在迫切需要的时候来到你身边,在精神错乱的边缘摇摇欲坠.从标题可以明显看出,这个问题是我在这里看到的其他几个问题的结合.

Stack overflow. Once again, I come to you in a time of dire need, teetering precariously on the brink of insanity. This question - as may be evident from the title - is an amalgamation of several other questions I have seen answered here.

我有一个PyQt应用程序,我想将stdout和stderr流重新路由到GUI 中没有延迟的QTextEdit中.

I have a PyQt application, and I want to re-route the stdout and stderr streams to a QTextEdit that is in my GUI without delay.

最初,我找到了以下堆栈溢出答案: https://stackoverflow.com/a/17145093/629404

这非常有效,但是有一个警告:如果在CPU处理相对较长的方法时多次更新stdout或stderr,则当主线程返回到应用程序循环时,所有更新同时显示.不幸的是,我有几种方法可能需要20秒钟才能完成(与网络相关),因此应用程序变得无响应-QTextEdit不会更新-直到它们完成.

This works perfectly, but with one caveat: If stdout or stderr are updated multiple times while the CPU is processing a relatively longer method, all of the updates show up simultaneously when the main thread returns to the application loop. Unfortunately, I have a few methods which take up to 20 seconds to complete (networking related), and so the application becomes unresponsive - and the QTextEdit does not update - until they are finished.

为了解决此问题,我将所有GUI处理都委派给了主线程,并且正在派生出第二个线程来处理更长的联网操作,并使用pyqtSignals通知主线程何时工作已完成并传递结果.当我开始测试以此方式编写的代码时,python解释器立即崩溃,没有任何警告.

In order to fix this problem, I delegated all of the GUI processing to the main thread, and I have been spawning off a second thread to handle the longer networking operations, using pyqtSignals to notify the main thread of when the work is finished and pass back results. Immediately when I began testing the code written this way, the python interpreter began crashing without any warning.

这是非常令人沮丧的地方:Python崩溃是因为-使用上述包含的链接中的类-我已将sys.stdout/err流分配给QTextEdit小部件; PyQt窗口小部件无法从应用程序线程之外的任何其他线程修改,并且由于对stdout和stderr的更新来自我创建的辅助工作线程,因此它们违反了此规则.我在其中重定向输出流的代码部分,可以肯定的是,程序运行时没有错误.

This is where it gets very frusterating: Python is crashing because - using the class from the included link above - I have assigned the sys.stdout/err streams to the QTextEdit widget; PyQt widgets cannot be modified from any thread other then the application thread, and since the updates to stdout and stderr are coming from the secondary worker thread that I created, they are violating this rule. I have commented out the section of code where I redirect the output streams, and sure enough, the program runs without error.

这使我回到第一位,并使我处于混乱的境地;假设我继续在主线程中处理与GUI相关的操作,并在辅助线程中处理计算和较长的操作(我已经理解这是防止应用程序在用户触发事件时被阻止的最佳方法),我该怎么办?从两个线程重定向Stdout和Stderr到QTextEdit小部件?上面链接中的类在主线程上工作正常,但由于上述原因-当更新来自第二个线程时,杀死了python.

This brings me back to square one, and leaves me in a confusing situation; Assuming I continue to handle GUI related operations in the main thread and deal with computation and longer operations in a secondary thread (which I have come to understand is the best way to keep the application from blocking when the user triggers events), how can I redirect Stdout and Stderr from both threads to the QTextEdit widget? The class in the link above works just fine for the main thread, but kills python - for the reason described above - when updates come from the second thread.

推荐答案

首先,+ 1是为了了解线程-不安全堆栈溢出的许多示例是多么的危险!

Firstly, +1 for realising how thread-unsafe many of the examples on stack overflow are!

解决方案是使用线程安全对象(如Python Queue.Queue)来中介信息的传递.我在下面附加了一些示例代码,这些示例代码将stdout重定向到Python Queue. Queue会读取此Queue,它会通过Qt的信号/插槽机制将内容发送到主线程(发出信号是线程安全的).然后,主线程将文本写入文本编辑.

The solution is to use a thread-safe object (like a Python Queue.Queue) to mediate the transfer of information. I've attached some sample code below which redirects stdout to a Python Queue. This Queue is read by a QThread, which emits the contents to the main thread through Qt's signal/slot mechanism (emitting signals is thread-safe). The main thread then writes the text to a text edit.

希望很清楚,如果不是,请随时提出问题!

Hope that is clear, feel free to ask questions if it is not!

请注意,提供的代码示例不能很好地清理QThreads,因此退出时会收到警告打印.我会留给您扩展您的用例并清理线程

Note that the code example provided doesn't clean up QThreads nicely, so you'll get warnings printed when you quit. I'll leave it to you to extend to your use case and clean up the thread(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_()

这篇关于从辅助线程将stdout和stderr重定向到PyQt4 QTextEdit的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆