Tkinter GUI 仅在移动鼠标时更新 [英] Tkinter GUI only updates when mouse is moved
问题描述
我正在运行一个 Tkinter GUI,它使用 subprocess.Popen(...)
分拆另一个进程(python 脚本),并使用管道进行 stdout 和 stderr.然后我将分离一个单独的线程,以异步读取该进程中的输出/错误,并使用 threading.Thread
将其绘制到 Tkinter 文本小部件中.
I am running a Tkinter GUI that spins off another process (python script) with subprocess.Popen(...)
and uses pipes for stdout and stderr. Then I'm spinning off a separate thread to asynchronously read the out/err from that process and draw it into a Tkinter Text widget using threading.Thread
.
除了异步之外,一切都很好.读取线程仅在我移动鼠标或按下键盘上的键时执行.我什至将打印语句放入线程函数中,当我在圆圈中移动鼠标时,它们开始/停止打印.
Everything works great except that the async. read thread only executes when I'm moving the mouse or pressing keys on the keyboard. I even put print statements into the threaded function and they start/stop printing when I move the mouse around in circles.
这是我正在使用的异步读取类,从此处借用:
Here's the async read class that I'm using, borrowed from 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()
还有我用于从异步文件读取器中提取消息的消耗方法(这是在单独线程上运行的方法:
And my consume method for pulling messages out of the async file reader (this is the one that runs on a separate thread:
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 中)仅在鼠标/键盘活动发生时执行......有任何想法吗?
Like I said before -- the consume()
thread only executes when I move the mouse or type on the keyboard -- which means the writeToLog(...)
function (for appending text into the Tkinter GUI) only gets executed when mouse/keyboard activity happens... Any ideas?
我想我可能知道发生了什么......如果我评论 writeToLog(...)
调用并将其替换为简单的打印(将 Tkinter 排除在外)然后消费线程正常执行.似乎 Tkinter 是这里的问题.关于我可以从消费线程完成 Tkinter 文本小部件更新的任何想法?
I think I might have an idea of what's happening... If I comment the writeToLog(...)
call and replace it with a simple print (taking Tkinter out of the equation) then the consume thread executes normally. It seems Tkinter is the problem here. Any ideas on I can accomplish the Tkinter text-widget update from the consume thread?
多亏了评论,它才能工作.这是我使用的最终代码:
Got it working thanks to the comments. Here's is the final code that I used:
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 控制台框架并在框架初始化程序的末尾调用一次:
Added this method to my Tkinter console frame and called it once at the end of the frame initializer:
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)
推荐答案
Tkinter 不是线程安全的.如果您的 writeToLog
函数尝试将数据插入文本小部件,您将获得不可预测的行为.为了让单独的线程将数据发送到小部件,您需要将数据写入线程安全队列,然后让主线程轮询该队列(使用 tkinter 的 after
方法).
Tkinter isn't thread safe. If your writeToLog
function tries to insert data into the text widget, you'll get unpredictable behavior. In order for a separate thread to send data to a widget you'll need to write the data to a thread-safe queue, then have your main thread poll that queue (using tkinter's after
method).
这篇关于Tkinter GUI 仅在移动鼠标时更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!