我在堆栈溢出上花费了大约6个小时,重写了我的python代码,并试图使其正常工作。只是没有。不管我做什么。
目标: 使子流程的输出实时显示在tkinter文本框中。
问题是: 我不知道如何使Popen实时工作。它似乎挂起,直到该过程完成。(独立运行,该过程完全按预期工作,所以只有这个东西有错误)
相关代码:
import os import tkinter import tkinter.ttk as tk import subprocess class Application (tk.Frame): process = 0 def __init__ (self, master=None): tk.Frame.__init__(self, master) self.grid() self.createWidgets() def createWidgets (self): self.quitButton = tk.Button(self, text='Quit', command=self.quit) self.quitButton.grid() self.console = tkinter.Text(self) self.console.config(state=tkinter.DISABLED) self.console.grid() def startProcess (self): dir = "C:/folder/" self.process = subprocess.Popen([ "python", "-u", dir + "start.py" ], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dir) self.updateLines() def updateLines (self): self.console.config(state=tkinter.NORMAL) while True: line = self.process.stdout.readline().decode().rstrip() if line == '' and self.process.poll() != None: break else: self.console.insert(tkinter.END, line + "\n") self.console.config(state=tkinter.DISABLED) self.after(1, self.updateLines) app = Application() app.startProcess() app.mainloop()
此外,如果代码编写不当,请随时销毁我的代码。这是我的第一个python项目,我预计该语言不会再有什么优势。
这里的问题是process.stdout.readline()将阻塞直到有完整的行可用为止。这意味着line == ''直到流程退出,该条件才被满足。您有两种选择。
process.stdout.readline()
line == ''
首先,您可以将stdout设置为非阻塞并自己管理缓冲区。它看起来像这样。编辑:正如Terry Jan Reedy指出的那样,这是仅Unix的解决方案。第二种选择应该是首选。
import fcntl ... def startProcess(self): self.process = subprocess.Popen(['./subtest.sh'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0) # prevent any unnecessary buffering # set stdout to non-blocking fd = self.process.stdout.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) # schedule updatelines self.after(100, self.updateLines) def updateLines(self): # read stdout as much as we can line = '' while True: buff = self.process.stdout.read(1024) if buff: buff += line.decode() else: break self.console.config(state=tkinter.NORMAL) self.console.insert(tkinter.END, line) self.console.config(state=tkinter.DISABLED) # schedule callback if self.process.poll() is None: self.after(100, self.updateLines)
第二种选择是让一个单独的线程将行读入队列。然后从队列中弹出更新行。看起来像这样
from threading import Thread from queue import Queue, Empty def readlines(process, queue): while process.poll() is None: queue.put(process.stdout.readline()) ... def startProcess(self): self.process = subprocess.Popen(['./subtest.sh'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) self.queue = Queue() self.thread = Thread(target=readlines, args=(self.process, self.queue)) self.thread.start() self.after(100, self.updateLines) def updateLines(self): try: line = self.queue.get(False) # False for non-blocking, raises Empty if empty self.console.config(state=tkinter.NORMAL) self.console.insert(tkinter.END, line) self.console.config(state=tkinter.DISABLED) except Empty: pass if self.process.poll() is None: self.after(100, self.updateLines)
穿线路线可能更安全。我不太肯定将stdout设置为non-blocking可以在所有平台上使用。