简体   繁体   English

将stdout和stderr从辅助线程重定向到PyQt4 QTextEdit

[英]Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread

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. 这个问题 - 从标题中可以看出 - 是我在这里回答的其他几个问题的合并。

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 . 我有一个PyQt应用程序,我想将stdout和stderr流重新路由到我的GUI中的QTextEdit, 没有延迟

Initially, I found the following stack overflow answer: https://stackoverflow.com/a/17145093/629404 最初,我发现以下堆栈溢出答案: https //stackoverflow.com/a/17145093/629404

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 . 这很有效,但有一点需要注意:如果在CPU处理相对较长的方法时多次更新stdout或stderr,则当主线程返回到应用程序循环时所有更新都会同时显示 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. 不幸的是,我有一些方法需要20秒才能完成(网络相关),因此应用程序变得无响应 - 并且QTextEdit不会更新 - 直到它们完成。

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. 为了解决这个问题, 我将所有GUI处理委托给主线程,并且我已经产生了第二个线程以处理更长的网络操作,使用pyqtSignals通知主线程工作何时完成并通过回来的结果。 Immediately when I began testing the code written this way, the python interpreter began crashing without any warning. 当我开始测试以这种方式编写的代码时,python解释器立刻开始崩溃而没有任何警告。

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; 这是非常令人讨厌的地方:Python正在崩溃,因为 - 使用上面包含的链接中的类 - 我已经将sys.stdout / err流分配给QTextEdit小部件; 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. PyQt小部件不能从除应用程序线程之外的任何线程修改,并且由于对stdout和stderr的更新来自我创建的辅助工作线程,因此它们违反了此规则。 I have commented out the section of code where I redirect the output streams, and sure enough, the program runs without error. 我已经注释掉了我重定向输出流的代码部分,果然,程序运行没有错误。

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? 假设我继续在主线程中处理GUI相关操作并处理辅助线程中的计算和更长时间的操作(我已经理解这是在用户触发事件时阻止应用程序阻塞的最佳方法),我该怎么办将Stdout和Stderr从两个线程重定向到QTextEdit小部件? 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. 上面链接中的类对于主线程来说效果很好,但是当更新来自第二个线程时,由于上述原因会杀死python。

Firstly, +1 for realising how thread- unsafe many of the examples on stack overflow are! 首先,+1用于实现堆栈溢出中的许多示例如何线程不安全

The solution is to use a thread-safe object (like a Python Queue.Queue ) to mediate the transfer of information. 解决方案是使用线程安全对象(如Python Queue.Queue )来调解信息传输。 I've attached some sample code below which redirects stdout to a Python Queue . 我在下面添加了一些示例代码,将stdout重定向到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). QueueQThread读取, QThread通过Qt的信号/槽机制将内容发送到主线程(发出信号是线程安全的)。 The main thread then writes the text to a text edit. 然后主线程将文本写入文本编辑。

Hope that is clear, feel free to ask questions if it is not! 希望很清楚,如果不是,请随时提问!

EDIT: Note that the code example provided doesn't clean up QThreads nicely, so you'll get warnings printed when you quit. 编辑:请注意,提供的代码示例不能很好地清理QThreads,因此您在退出时会收到警告。 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_()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM