web-dev-qa-db-ja.com

stdoutとstderrをセカンダリスレッドからPyQt4 QTextEditにリダイレクトする

スタックオーバーフロー。もう一度、私は、狂気の瀬戸際に不安定にぐちゃぐちゃになって、悲惨な必要の時にあなたのところへ行きます。この質問は、タイトルからわかるように、ここで回答した他のいくつかの質問をまとめたものです。

PyQtアプリケーションがあり、stdoutとstderrストリームを、GUIにあるQTextEditに再ルーティングしたい遅延なし

最初に、次のスタックオーバーフローの答えを見つけました:https://stackoverflow.com/a/17145093/629404

これは完全に機能しますが、1つの注意点があります。CPUが比較的長いメソッドを処理している間にstdoutまたはstderrが複数回更新される場合、メインスレッドがに戻ると、すべての更新が同時に表示されます。アプリケーションループ。残念ながら、完了するまでに最大20秒かかるいくつかのメソッド(ネットワーク関連)があるため、アプリケーションが応答しなくなり、完了するまでQTextEditは更新されません。

この問題を修正するために、すべてのGUI処理をメインスレッドに委任し、pyqtSignalsを使用してメインネットワークに通知することで、より長いネットワーク操作を処理するために2番目のスレッドを生成しましたこのように記述されたコードのテストを開始した直後に、pythonインタープリターは警告なしにクラッシュし始めました。

これは非常にイライラするところです:Pythonがクラッシュしているのは-上のインクルードリンクからのクラスを使用している-私はsys.stdout/errストリームをQTextEditウィジェットに割り当てた;PyQtウィジェットは、アプリケーションスレッド以外のスレッドからは変更できません。stdoutとstderrへの更新は、私が作成したセカンダリワーカースレッドから行われるため、このルールに違反しています。出力ストリームをリダイレクトするコードのセクションをコメント化しました。確かに、プログラムはエラーなしで実行されます。

これにより、私は元の状態に戻り、混乱する状況に陥ります。メインスレッドでGUI関連の操作を処理し続け、セカンダリスレッドで計算とより長い操作を処理すると仮定すると(ユーザーがイベントをトリガーしたときにアプリケーションがブロックされないようにするための最良の方法です)、どうすればよいですか? StdoutとStderrを両方のスレッドからQTextEditウィジェットにリダイレクトしますか?上記のリンクのクラスはメインスレッドでは問題なく機能しますが、2番目のスレッドから更新が行われると、python-上記の理由により)が強制終了されます。

26
araisbec

まず、どのようにスレッドを実現するための+1 -nsafeスタックオーバーフローの例の多くは!

解決策は、スレッドセーフなオブジェクトを使用することです(Python Queue.Queue)情報の転送を仲介します。 stdoutをPython Queueにリダイレクトするサンプルコードを以下に添付しました。このQueueQThreadは、Qtのシグナル/スロットメカニズムを介してメインスレッドにコンテンツを発行します(シグナルの発行はスレッドセーフです)次に、メインスレッドはテキストをテキスト編集に書き込みます。

それが明確であることを願って、そうでない場合は気軽に質問してください!

編集:提供されているコード例は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_()
23