Tkinter GUI仅在移动鼠标时更新

时间:2012-08-05 22:58:50

标签: python multithreading tkinter

我正在运行一个Tkinter GUI,它使用subprocess.Popen(...)旋转另一个进程(python脚本),并使用stdout和stderr的管道。然后,我将关闭一个单独的线程,以异步方式从该进程读取out / err,并使用threading.Thread将其绘制到Tkinter Text小部件中。

除了异步之外,一切都很好。读取线程仅在我移动鼠标或按键盘上的键时执行。我甚至将print语句放入线程函数中,当我将鼠标移动到圆圈中时,它们会开始/停止打印。

这是我正在使用的异步读类,借用here

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)

1 个答案:

答案 0 :(得分:3)

Tkinter不是线程安全的。如果您的writeToLog函数尝试将数据插入文本小部件,您将获得不可预测的行为。为了使单独的线程将数据发送到窗口小部件,您需要将数据写入线程安全的队列,然后让主线程轮询该队列(使用tkinter的after方法)。