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

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (1)
  • 关注 (0)
  • 查看 (42)

堆栈溢出。我又一次来到你面前,在一个急需的时刻,在危险的边缘摇摇欲坠。这个问题---从标题中可以看出---是我在这里看到的其他几个问题的综合。

我有一个PyQt应用程序,我想将stdout和stderr流重新路由到我的GUI中的QTextEdit刻不容缓.

这是完美的,但有一个警告:如果stdout或stderr在CPU处理相对较长的方法时被多次更新。不幸的是,我有几个方法需要20秒才能完成(与网络有关),因此应用程序会变得没有响应性--QTextEdit在它们完成之前不会更新。

为了解决这个问题,我将所有GUI处理委托给主线程,并且我已经生成了第二个线程来处理较长的网络操作,使用pyqtSignals通知主线程工作何时完成并返回结果。当我开始测试以这种方式编写的代码时,python解释器就开始在没有任何警告的情况下崩溃。

这是它非常节约的地方:Python崩溃了,因为--使用上面包含的链接中的类--我将sys.stdout/err流分配给QTextEdit小部件;不能从应用程序线程以外的任何线程修改PyQt小部件,而且由于stdout和stderr的更新来自我创建的辅助工作线程,因此它们违反了这条规则。我已经注释掉了我重定向输出流的代码部分,并且足够肯定的是,程序运行时没有错误。

;假设我继续在主线程中处理与GUI相关的操作,并在第二个线程中处理计算和更长的操作),我如何将Stdout和Stderr从两个线程重定向到QTextEdit小部件?上面链接中的类对于主线程运行良好,但是当更新来自第二个线程时会杀死python。

提问于
用户回答回答于

来认识到线程-不安全堆栈溢出的许多例子是!解决方案是使用线程安全对象(如Python)。Queue.Queue)调解信息的传递。我附加了一些示例代码,在下面重定向stdout致PythonQueue...。这QueueQThread,它通过Qt的信号/插槽机制(发射信号是线程安全的)将内容发送到主线程。然后主线程将文本写入文本编辑。

编辑:请注意,提供的代码示例没有很好地清理QThread,因此退出时将得到打印的警告。

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

扫码关注云+社区