我有一个 Tkinter GUI 程序,我想用被调用进程的输出来更新文本框架。我设法使用线程和子进程使其工作得很好,将“运行”按钮绑定到一个方法来创建线程并在其中调用 Popen。
def template_thread(self):
threading.Thread(target=self.run_template, daemon=True).start()
run_template方法调用main.py,并将配置文件的路径作为参数:
p = subprocess.Popen([EXE, FIRST_RUN, os.path.join(self.output.get(), 'runtime.ini')],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, universal_newlines=True)
while p.poll() is None:
print('here')
#p.stdout.flush()
#p.stderr.flush()
msg = p.stdout.readline()
err = p.stderr.readline()
if msg:
self.output_window.insert(tk.END, msg + "\n")
if err:
with open(os.path.join(self.output.get(), 'LOG.txt'), 'a') as log:
log.write(err)
每当我在 Pycharm 中测试它时,它都可以完美地工作,但是一旦我尝试从其他任何地方运行它,行为就会改变为阻塞 readline() 直到进程完成,然后将整个标准输出刷新到文本框架立刻。我认为从其他类似但非常古老的问题来看,它与每个程序如何设置缓冲区有关,并且 IDE 终端以某种方式将缓冲区设置为刷新和换行符,但找不到解决方案。使用 '-u' 作为参数运行似乎没有效果,并且在第一个打印语句中添加 'flush=True' 也没有效果。该程序按需要工作,否则主循环不会中断,因为我可以看到我的小部件的焦点发生变化,所以我有点困惑。如果有什么改变的话,在 Windows 上运行。如有任何帮助,我们将不胜感激!
在查看@TimRoberts 评论后,我设法使用 after() 和另外两个线程来读取和写入队列,使其工作。相关的类方法有:
def update_window(self):
try:
msg = self.queue.get_nowait()
except queue.Empty:
msg = None
if msg:
self.output_window.insert(tk.END, msg + "\n")
if msg != '__EXIT__':
self.root.after(1000, self.update_window)
def run_template(self, queue, control_queue):
p = subprocess.Popen([EXE, FIRST_RUN, os.path.join(self.output.get(), 'runtime.ini')], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, universal_newlines=True)
listner = threading.Thread(target=update_queue, args=[p.stdout, queue], daemon=True)
listner.start()
p.wait()
if p.returncode != 0:
messagebox.showerror("Runtime Error", "Error has occurred during runtime")
with open(os.path.join(self.output.get(), 'LOG.txt'), 'a') as log:
log.write(p.stderr.read())
control_queue.put('Unlock')
else:
ret = messagebox.askyesno("Process Completed", "Run Again?")
if ret:
control_queue.put('Unlock')
else:
control_queue.put('Exit')
queue.put('__EXIT__')
def template_thread(self):
self.lock_widgets(self.root)
proc = self.root.after(1000, self.update_window)
proc2 = self.root.after(1000, self.control_listener)
thread = threading.Thread(target=self.run_template, args=[self.queue, self.control_queue], daemon=True).start()
和一个静态函数:
def update_queue(pipe, queue):
while True:
line = pipe.readline()
if not line:
break
queue.put(line)
这似乎工作得很好,但我不确定这是否是最好的方法,所以我将保留这个问题。