3

別のプロセス (python スクリプト) をスピンオフしsubprocess.Popen(...)、stdout と stderr にパイプを使用する Tkinter GUI を実行しています。次に、別のスレッドをスピンオフして、そのプロセスから out/err を非同期に読み取り、 を使用して Tkinter Text ウィジェットに描画しますthreading.Thread

非同期を除いて、すべてがうまく機能します。read thread は、マウスを動かしたり、キーボードのキーを押したりしているときにのみ実行されます。スレッド化された関数に印刷ステートメントを入れて、マウスを円で動かすと印刷を開始/停止します。

ここから借りた、私が使用している非同期読み取りクラスは次のとおりです。

class AsynchronousFileReader(threading.Thread):
    '''
    Helper class to implement asynchronous reading of a file
    in a separate thread. Pushes read lines on a queue to
    be consumed in another thread.
    '''

    def __init__(self, fd, queue):
        assert isinstance(queue, Queue.Queue)
        assert callable(fd.readline)
        threading.Thread.__init__(self)
        self._fd = fd
        self._queue = queue

    def run(self):
        '''The body of the tread: read lines and put them on the queue.'''
        for line in iter(self._fd.readline, ''):
            self._queue.put(line)

    def eof(self):
        '''Check whether there is no more content to expect.'''
        return not self.is_alive() and self._queue.empty()

そして、非同期ファイルリーダーからメッセージを引き出すための私の消費メソッド(これは別のスレッドで実行されるものです:

def consume(self, process, console_frame):
    # Launch the asynchronous readers of the process' stdout and stderr.

    stdout_queue = Queue.Queue()
    stdout_reader = AsynchronousFileReader(process.stdout, stdout_queue)
    stdout_reader.start()
    stderr_queue = Queue.Queue()
    stderr_reader = AsynchronousFileReader(process.stderr, stderr_queue)
    stderr_reader.start()

    # Check the queues if we received some output (until there is nothing more to get).
    while not stdout_reader.eof() or not stderr_reader.eof():
        # Show what we received from standard output.
        while not stdout_queue.empty():
            line = stdout_queue.get()
            console_frame.writeToLog(line.strip(), max_lines=None)
            time.sleep(.03) # prevents it from printing out in large blocks at a time

        # Show what we received from standard error.
        while not stderr_queue.empty():
            line = stderr_queue.get()
            console_frame.writeToLog(line.strip(), max_lines=None)
            time.sleep(.03) # prevents it from printing out in large blocks at a time

        # Sleep a bit before asking the readers again.
        time.sleep(.05)

    # Let's be tidy and join the threads we've started.
    stdout_reader.join()
    stderr_reader.join()

    # Close subprocess' file descriptors.
    process.stdout.close()
    process.stderr.close()

    print "finished executing"
    if self.stop_callback:
        self.stop_callback()

前に言ったように、consume()スレッドはマウスを動かしたり、キーボードで入力したときにのみ実行されます。つまり、writeToLog(...)(Tkinter GUI にテキストを追加するための) 関数は、マウス/キーボードのアクティビティが発生したときにのみ実行されます...何かアイデアはありますか?

編集:何が起こっているのかがわかると思います...writeToLog(...)呼び出しにコメントを付けて単純な印刷に置き換えると(Tkinterを方程式から外します)、消費スレッドは正常に実行されます。ここでTkinterが問題になっているようです。消費スレッドから Tkinter テキストウィジェットの更新を達成できるアイデアはありますか?

EDIT2:コメントのおかげでうまくいきました。これが私が使用した最終的なコードです:

gui_text_queue = Queue.Queue()


def consume(self, process, console_frame):
    # Launch the asynchronous readers of the process' stdout and stderr.

    stdout_queue = Queue.Queue()
    stdout_reader = AsynchronousFileReader(process.stdout, stdout_queue)
    stdout_reader.start()
    stderr_queue = Queue.Queue()
    stderr_reader = AsynchronousFileReader(process.stderr, stderr_queue)
    stderr_reader.start()

    # Check the queues if we received some output (until there is nothing more to get).
    while not stdout_reader.eof() or not stderr_reader.eof():
        # Show what we received from standard output.
        while not stdout_queue.empty():
            line = stdout_queue.get()
            gui_text_queue.put(line.strip())

        # Show what we received from standard error.
        while not stderr_queue.empty():
            line = stderr_queue.get()
            gui_text_queue.put(line.strip())

        # Sleep a bit before asking the readers again.
        time.sleep(.01)

    # Let's be tidy and join the threads we've started.
    stdout_reader.join()
    stderr_reader.join()

    # Close subprocess' file descriptors.
    process.stdout.close()
    process.stderr.close()

    if self.stop_callback:
        self.stop_callback()

このメソッドを Tkinter コンソール フレームに追加し、フレーム初期化子の最後で一度呼び出しました。

def pull_text_and_update_gui(self):
    while not gui_text_queue.empty():
        text = gui_text_queue.get()
        self.writeToLog(text, max_lines=None)
    self.after(5, self.pull_text_and_update_gui)
4

1 に答える 1

3

Tkinter はスレッドセーフではありません。関数writeToLogがテキスト ウィジェットにデータを挿入しようとすると、予期しない動作が発生します。別のスレッドがデータをウィジェットに送信するには、データをスレッドセーフ キューに書き込む必要があります。次に、メイン スレッドがそのキューをポーリングするようにします (tkinter のafterメソッドを使用)。

于 2012-08-05T23:16:23.780 に答える